@bendyline/gezel-inference 1.0.5 → 1.0.7

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 (144) hide show
  1. package/dist/cache/controller.d.ts +2 -0
  2. package/dist/cache/controller.js +38 -5
  3. package/dist/catalog/chat-models/index.json +1 -1
  4. package/dist/catalog/chat-models/lf/lfm3-1.7b-q4/manifest.json +114 -0
  5. package/dist/catalog/chat-models/lf/lfm3-1.7b-q4/versions/1.0.0/manifest.json +15 -0
  6. package/dist/catalog/chat-models/raw-index.json +1 -1
  7. package/dist/catalog/image-loras/index.json +1 -0
  8. package/dist/catalog/image-loras/kr/krea-2-ink-wash/manifest.json +31 -0
  9. package/dist/catalog/image-loras/kr/krea-2-ink-wash/versions/1.0.0/manifest.json +15 -0
  10. package/dist/catalog/image-loras/kr/krea-2-isometric-3d/manifest.json +32 -0
  11. package/dist/catalog/image-loras/kr/krea-2-isometric-3d/versions/1.0.0/manifest.json +15 -0
  12. package/dist/catalog/image-loras/kr/krea-2-kids-drawing/manifest.json +31 -0
  13. package/dist/catalog/image-loras/kr/krea-2-kids-drawing/versions/1.0.0/manifest.json +15 -0
  14. package/dist/catalog/image-loras/kr/krea-2-pixel-art/manifest.json +31 -0
  15. package/dist/catalog/image-loras/kr/krea-2-pixel-art/versions/1.0.0/manifest.json +15 -0
  16. package/dist/catalog/image-loras/kr/krea-2-soft-watercolor/manifest.json +31 -0
  17. package/dist/catalog/image-loras/kr/krea-2-soft-watercolor/versions/1.0.0/manifest.json +15 -0
  18. package/dist/catalog/image-loras/kr/krea-2-turbo-4step/manifest.json +31 -0
  19. package/dist/catalog/image-loras/kr/krea-2-turbo-4step/versions/1.0.0/manifest.json +15 -0
  20. package/dist/catalog/image-loras/mo/moss-image-gouache/manifest.json +29 -0
  21. package/dist/catalog/image-loras/mo/moss-image-gouache/versions/1.0.0/manifest.json +15 -0
  22. package/dist/catalog/image-loras/mo/moss-image-sft-4step/manifest.json +30 -0
  23. package/dist/catalog/image-loras/mo/moss-image-sft-4step/versions/1.0.0/manifest.json +15 -0
  24. package/dist/catalog/image-loras/qw/qwen-image-edit-2511-lightning/manifest.json +31 -0
  25. package/dist/catalog/image-loras/qw/qwen-image-edit-2511-lightning/versions/1.0.0/manifest.json +15 -0
  26. package/dist/catalog/image-loras/raw-index.json +1 -0
  27. package/dist/catalog/image-loras/sd/sdxl-3d-render/manifest.json +29 -0
  28. package/dist/catalog/image-loras/sd/sdxl-3d-render/versions/1.0.0/manifest.json +15 -0
  29. package/dist/catalog/image-loras/sd/sdxl-coloring-book/manifest.json +30 -0
  30. package/dist/catalog/image-loras/sd/sdxl-coloring-book/versions/1.0.0/manifest.json +15 -0
  31. package/dist/catalog/image-loras/sd/sdxl-papercut/manifest.json +29 -0
  32. package/dist/catalog/image-loras/sd/sdxl-papercut/versions/1.0.0/manifest.json +15 -0
  33. package/dist/catalog/image-loras/sd/sdxl-pixel-art/manifest.json +29 -0
  34. package/dist/catalog/image-loras/sd/sdxl-pixel-art/versions/1.0.0/manifest.json +15 -0
  35. package/dist/catalog/image-models/fl/flux-2-klein-4b-q4/manifest.json +3 -0
  36. package/dist/catalog/image-models/index.json +1 -1
  37. package/dist/catalog/image-models/qw/qwen-image-edit-2511-q4/manifest.json +44 -0
  38. package/dist/catalog/image-models/qw/qwen-image-edit-2511-q4/versions/1.0.0/manifest.json +36 -0
  39. package/dist/catalog/image-models/raw-index.json +1 -1
  40. package/dist/catalog/knowledge-catalogs/index.json +1 -1
  41. package/dist/catalog/knowledge-catalogs/raw-index.json +1 -1
  42. package/dist/catalog/knowledge-catalogs/wi/wikipedia-arts/versions/2026.10.2/manifest.json +181 -0
  43. package/dist/catalog/knowledge-catalogs/wi/wikipedia-astronomy/versions/2026.10.2/manifest.json +181 -0
  44. package/dist/catalog/knowledge-catalogs/wi/wikipedia-biology/versions/2026.10.2/manifest.json +217 -0
  45. package/dist/catalog/knowledge-catalogs/wi/wikipedia-business/versions/2026.10.2/manifest.json +197 -0
  46. package/dist/catalog/knowledge-catalogs/wi/wikipedia-film-tv/versions/2026.10.2/manifest.json +197 -0
  47. package/dist/catalog/knowledge-catalogs/wi/wikipedia-food-drink/versions/2026.10.2/manifest.json +185 -0
  48. package/dist/catalog/knowledge-catalogs/wi/wikipedia-games/versions/2026.10.2/manifest.json +181 -0
  49. package/dist/catalog/knowledge-catalogs/wi/wikipedia-general/manifest.json +55 -0
  50. package/dist/catalog/knowledge-catalogs/wi/wikipedia-general/versions/2026.10.2/manifest.json +165 -0
  51. package/dist/catalog/knowledge-catalogs/wi/wikipedia-geography/versions/2026.10.2/manifest.json +217 -0
  52. package/dist/catalog/knowledge-catalogs/wi/wikipedia-history/versions/2026.10.2/manifest.json +217 -0
  53. package/dist/catalog/knowledge-catalogs/wi/wikipedia-language/versions/2026.10.2/manifest.json +181 -0
  54. package/dist/catalog/knowledge-catalogs/wi/wikipedia-law/versions/2026.10.2/manifest.json +185 -0
  55. package/dist/catalog/knowledge-catalogs/wi/wikipedia-literature/versions/2026.10.2/manifest.json +193 -0
  56. package/dist/catalog/knowledge-catalogs/wi/wikipedia-mathematics/versions/2026.10.2/manifest.json +189 -0
  57. package/dist/catalog/knowledge-catalogs/wi/wikipedia-medicine/versions/2026.10.2/manifest.json +185 -0
  58. package/dist/catalog/knowledge-catalogs/wi/wikipedia-military/versions/2026.10.2/manifest.json +189 -0
  59. package/dist/catalog/knowledge-catalogs/wi/wikipedia-music/versions/2026.10.2/manifest.json +197 -0
  60. package/dist/catalog/knowledge-catalogs/wi/wikipedia-people-arts/versions/2026.10.2/manifest.json +209 -0
  61. package/dist/catalog/knowledge-catalogs/wi/wikipedia-people-business/versions/2026.10.2/manifest.json +189 -0
  62. package/dist/catalog/knowledge-catalogs/wi/wikipedia-people-entertainment/versions/2026.10.2/manifest.json +201 -0
  63. package/dist/catalog/knowledge-catalogs/wi/wikipedia-people-military/versions/2026.10.2/manifest.json +185 -0
  64. package/dist/catalog/knowledge-catalogs/wi/wikipedia-people-other/versions/2026.10.2/manifest.json +165 -0
  65. package/dist/catalog/knowledge-catalogs/wi/wikipedia-people-politics/versions/2026.10.2/manifest.json +197 -0
  66. package/dist/catalog/knowledge-catalogs/wi/wikipedia-people-religion/versions/2026.10.2/manifest.json +201 -0
  67. package/dist/catalog/knowledge-catalogs/wi/wikipedia-people-science/versions/2026.10.2/manifest.json +205 -0
  68. package/dist/catalog/knowledge-catalogs/wi/wikipedia-people-sports/versions/2026.10.2/manifest.json +237 -0
  69. package/dist/catalog/knowledge-catalogs/wi/wikipedia-politics/versions/2026.10.2/manifest.json +197 -0
  70. package/dist/catalog/knowledge-catalogs/wi/wikipedia-religion-philosophy/versions/2026.10.2/manifest.json +189 -0
  71. package/dist/catalog/knowledge-catalogs/wi/wikipedia-science/versions/2026.10.2/manifest.json +185 -0
  72. package/dist/catalog/knowledge-catalogs/wi/wikipedia-sports/versions/2026.10.2/manifest.json +193 -0
  73. package/dist/catalog/knowledge-catalogs/wi/wikipedia-technology/versions/2026.10.2/manifest.json +185 -0
  74. package/dist/catalog/knowledge-catalogs/wi/wikipedia-transportation/versions/2026.10.2/manifest.json +189 -0
  75. package/dist/catalog/provenance.json +2 -1
  76. package/dist/engine-components.js +2 -2
  77. package/dist/engines/electron-native-reuse.d.ts +7 -1
  78. package/dist/engines/electron-native-reuse.js +12 -4
  79. package/dist/engines/native-file-manifest.json +546 -406
  80. package/dist/engines/native-manifest.d.ts +4 -4
  81. package/dist/engines/native-manifest.js +36 -36
  82. package/dist/engines/resolver.d.ts +1 -1
  83. package/dist/engines/resolver.js +48 -51
  84. package/dist/http/auth-context.d.ts +12 -0
  85. package/dist/http/routes/image-ensure-routes.d.ts +4 -0
  86. package/dist/http/routes/image-ensure-routes.js +102 -0
  87. package/dist/http/routes/v1-image-styles.d.ts +5 -0
  88. package/dist/http/routes/v1-image-styles.js +91 -0
  89. package/dist/http/routes/v1-images.d.ts +1 -0
  90. package/dist/http/routes/v1-images.js +15 -93
  91. package/dist/inference-service.js +2 -1
  92. package/dist/knowledge/worker-host.js +2 -0
  93. package/dist/machine-engine/inspect-runtime.js +12 -3
  94. package/dist/memory/embed-core.js +3 -0
  95. package/dist/memory/embeddings.js +2 -0
  96. package/dist/models/image-fit.js +2 -2
  97. package/dist/models/weight-facts.d.ts +5 -0
  98. package/dist/models/weight-facts.js +5 -2
  99. package/dist/providers/ds4/build-provider.js +1 -1
  100. package/dist/providers/ds4/residency.js +3 -3
  101. package/dist/providers/image/img2img-support.d.ts +36 -1
  102. package/dist/providers/image/img2img-support.js +30 -1
  103. package/dist/providers/image/local-engine.d.ts +1 -0
  104. package/dist/providers/image/local-engine.js +8 -2
  105. package/dist/providers/image/local-factory.d.ts +10 -1
  106. package/dist/providers/image/local-factory.js +33 -6
  107. package/dist/providers/image/mock.d.ts +8 -1
  108. package/dist/providers/image/mock.js +46 -1
  109. package/dist/providers/image/pull-registry.d.ts +25 -3
  110. package/dist/providers/image/pull-registry.js +96 -34
  111. package/dist/providers/image/sd-cpp.d.ts +63 -4
  112. package/dist/providers/image/sd-cpp.js +361 -38
  113. package/dist/providers/image/types.d.ts +76 -4
  114. package/dist/providers/image/types.js +8 -0
  115. package/dist/providers/llama-cpp/build-provider.js +3 -2
  116. package/dist/providers/llama-cpp/cache-adapter.d.ts +3 -0
  117. package/dist/providers/llama-cpp/cache-adapter.js +18 -4
  118. package/dist/providers/llama-cpp/devices.js +2 -2
  119. package/dist/providers/llama-cpp/engine-flags.d.ts +5 -0
  120. package/dist/providers/llama-cpp/engine-flags.js +5 -2
  121. package/dist/providers/llama-cpp/provider.js +20 -4
  122. package/dist/providers/mlx/provider.js +13 -4
  123. package/dist/providers/mlx/python/gezel_mlx_server.py +9 -2
  124. package/dist/providers/mlx/python/spec_decode.py +44 -5
  125. package/dist/providers/native/capacity-broker.d.ts +1 -1
  126. package/dist/providers/native/capacity-broker.js +8 -8
  127. package/dist/providers/native/capacity-budget.js +5 -4
  128. package/dist/providers/native/device-capacity-ledger.d.ts +2 -0
  129. package/dist/providers/native/device-capacity-ledger.js +36 -5
  130. package/dist/providers/native/device-capacity.js +4 -3
  131. package/dist/providers/native/local-engine-runtime.js +4 -4
  132. package/dist/providers/native/process-identity.d.ts +2 -0
  133. package/dist/providers/native/process-identity.js +70 -0
  134. package/dist/relevance/relevance-core.js +2 -0
  135. package/dist/relevance/relevance-model.js +2 -0
  136. package/dist/relevance/settings.js +2 -2
  137. package/dist/runtime-lock.d.ts +43 -5
  138. package/dist/runtime-lock.js +189 -26
  139. package/dist/system/host-memory.d.ts +46 -0
  140. package/dist/system/host-memory.js +89 -0
  141. package/dist/system/memory.js +5 -4
  142. package/dist/transformers-cache.js +2 -0
  143. package/dist/utils/download-common.d.ts +1 -1
  144. package/package.json +8 -8
@@ -6,6 +6,8 @@ import { imageCatalogFits, imageModelFit, wontRunRefusal } from '../../models/im
6
6
  import { ReadOnlyModelError, modelExistsOnlyReadOnly, modelStorageRoots, } from '../../models/storage-roots.js';
7
7
  import { NoLocalImageModelError } from '../../providers/image/local-factory.js';
8
8
  import { ImageCapacityError, ImageGenerationCancelledError, } from '../../providers/image/types.js';
9
+ import { registerImageEnsureRoutes } from './image-ensure-routes.js';
10
+ import { v1ImageStylesRoutes } from './v1-image-styles.js';
9
11
  const log = createLogger('v1-images');
10
12
  /** The one local image engine this host serves. */
11
13
  const ENGINE = 'sd-cpp';
@@ -36,6 +38,7 @@ function describeManifest(manifest) {
36
38
  description: manifest.description,
37
39
  download_bytes: downloadBytes(manifest),
38
40
  ...(license ? { license } : {}),
41
+ ...(manifest.licenseUrl ? { license_url: manifest.licenseUrl } : {}),
39
42
  commercial_use: manifest.commercialUse,
40
43
  ...(manifest.rendersText !== undefined ? { renders_text: manifest.rendersText } : {}),
41
44
  ...(manifest.promptStyle ? { prompt_style: manifest.promptStyle } : {}),
@@ -49,36 +52,6 @@ function describeManifest(manifest) {
49
52
  : {}),
50
53
  };
51
54
  }
52
- function ensureEvent(jobId, event, totalBytes) {
53
- const job = { jobId, modelId: qualified(jobId) };
54
- switch (event.type) {
55
- case 'progress':
56
- return {
57
- ...job,
58
- type: 'progress',
59
- bytesWritten: event.bytesWritten,
60
- totalBytes: event.totalBytes ?? totalBytes,
61
- };
62
- case 'retrying':
63
- return {
64
- ...job,
65
- type: 'retrying',
66
- attempt: event.attempt,
67
- maxAttempts: event.maxAttempts,
68
- delayMs: event.delayMs,
69
- reason: event.reason,
70
- };
71
- case 'error':
72
- return { ...job, type: 'error', error: event.error };
73
- case 'done':
74
- return { ...job, type: 'done' };
75
- }
76
- }
77
- function pullStatus(pull) {
78
- if (!pull.finished)
79
- return 'running';
80
- return pull.error ? 'error' : 'done';
81
- }
82
55
  function generationError(err) {
83
56
  const message = err instanceof Error ? err.message : String(err);
84
57
  if (err instanceof ImageGenerationCancelledError)
@@ -117,6 +90,8 @@ function generationError(err) {
117
90
  */
118
91
  export function v1ImagesRoutes(ctx) {
119
92
  const app = new Hono();
93
+ if (ctx.images.loraPulls)
94
+ app.route('/styles', v1ImageStylesRoutes(ctx, ctx.images.loraPulls));
120
95
  const fitContext = { catalog: ctx.catalog, readConfig: () => ctx.store.readConfig() };
121
96
  app.get('/models', async (c) => {
122
97
  const provider = await ctx.images.current();
@@ -200,69 +175,7 @@ export function v1ImagesRoutes(ctx) {
200
175
  }
201
176
  return c.json({ status: 'downloading', model_id: qualified(id), job_id: id }, 202);
202
177
  });
203
- app.get('/models/ensure/:jobId', (c) => {
204
- const pull = ctx.images.pulls.get(c.req.param('jobId'));
205
- if (!pull)
206
- return c.json(apiError('job_not_found', 'job not found'), 404);
207
- return c.json({
208
- job_id: pull.id,
209
- model_id: qualified(pull.id),
210
- status: pullStatus(pull),
211
- started_at: Date.parse(pull.startedAt),
212
- bytes_written: pull.bytesWritten,
213
- total_bytes: pull.totalBytes,
214
- ...(pull.error ? { error: pull.error } : {}),
215
- });
216
- });
217
- app.get('/models/ensure/:jobId/events', (c) => {
218
- const jobId = c.req.param('jobId');
219
- const snapshot = ctx.images.pulls.get(jobId);
220
- if (!snapshot)
221
- return c.json(apiError('job_not_found', 'job not found'), 404);
222
- return streamSSE(c, async (stream) => {
223
- let closed = false;
224
- let sawError = false;
225
- const unsubscribe = ctx.images.pulls.subscribe(jobId, (event) => {
226
- // A failed pull ends with an error and then its done; the error is terminal.
227
- if (sawError)
228
- return;
229
- if (event.type === 'error')
230
- sawError = true;
231
- const terminal = event.type === 'done' || event.type === 'error';
232
- void stream
233
- .writeSSE({ data: JSON.stringify(ensureEvent(jobId, event, snapshot.totalBytes)) })
234
- .then(() => {
235
- if (terminal)
236
- closed = true;
237
- })
238
- .catch(() => {
239
- closed = true;
240
- });
241
- });
242
- if (!unsubscribe)
243
- return;
244
- stream.onAbort(() => {
245
- closed = true;
246
- unsubscribe();
247
- });
248
- while (!closed) {
249
- await stream.sleep(1000);
250
- try {
251
- await stream.writeSSE({ event: 'ping', data: '' });
252
- }
253
- catch {
254
- closed = true;
255
- }
256
- }
257
- unsubscribe();
258
- });
259
- });
260
- app.delete('/models/ensure/:jobId', (c) => {
261
- if (!ctx.images.pulls.cancel(c.req.param('jobId'))) {
262
- return c.json(apiError('job_not_found', 'No download in progress for that job'), 404);
263
- }
264
- return c.json({ cancelled: true });
265
- });
178
+ registerImageEnsureRoutes(app, '/models/ensure', ctx.images.pulls, qualified);
266
179
  app.delete('/models/:id', async (c) => {
267
180
  const id = catalogIdOf(decodeURIComponent(c.req.param('id')));
268
181
  if (!id)
@@ -302,6 +215,14 @@ export function v1ImagesRoutes(ctx) {
302
215
  : apiError('no_local_image_model', new NoLocalImageModelError().message);
303
216
  return c.json(error, 409);
304
217
  }
218
+ const style = request.style
219
+ ? installed
220
+ .find((model) => model.id === modelId)
221
+ ?.loras?.find((lora) => lora.id === request.style && lora.purpose === 'style')
222
+ : undefined;
223
+ if (request.style && !style) {
224
+ return c.json(apiError('style_not_available', 'That style is not installed for this image model.'), 409);
225
+ }
305
226
  const item = await ctx.catalog.get('image-model', modelId).catch(() => null);
306
227
  const manifest = item?.manifest.kind === 'image-model' ? item.manifest : undefined;
307
228
  const { width, height } = resolveImageSize({
@@ -338,6 +259,7 @@ export function v1ImagesRoutes(ctx) {
338
259
  prompt: request.prompt,
339
260
  ...(request.negative_prompt ? { negativePrompt: request.negative_prompt } : {}),
340
261
  model: modelId,
262
+ ...(style ? { loras: [{ id: style.id }] } : {}),
341
263
  width,
342
264
  height,
343
265
  ...(request.seed !== undefined ? { seed: request.seed } : {}),
@@ -2,7 +2,7 @@ import { randomBytes, randomUUID, timingSafeEqual } from 'node:crypto';
2
2
  import { join } from 'node:path';
3
3
  import { GEZEL_API_GENERATION, GEZEL_API_GENERATION_FLOOR, GEZEL_VERSION, GezelConfigSchema, createLogger, getLogOutput, nowIso, setLogOutput, startSuspendMonitor, stopSuspendMonitor, } from '@bendyline/gezel';
4
4
  import { BundledSource, CatalogService } from '@bendyline/gezel-catalog';
5
- import { discoverNativeBinaries } from '@bendyline/gezel/native';
5
+ import { disableOnnxRuntimeTelemetry, discoverNativeBinaries } from '@bendyline/gezel/native';
6
6
  import { gezelHome } from '@bendyline/gezel/paths';
7
7
  import { Hono } from 'hono';
8
8
  import { bodyLimit } from 'hono/body-limit';
@@ -37,6 +37,7 @@ import './http/auth-context.js';
37
37
  const log = createLogger('inference');
38
38
  /** Direct Fetch host: no listener, product store, background downloads or daemon discovery. */
39
39
  export async function startService(options = {}) {
40
+ disableOnnxRuntimeTelemetry();
40
41
  const previousLogOutput = getLogOutput();
41
42
  const restoreLogOutput = () => setLogOutput(previousLogOutput);
42
43
  if (options.logOutput)
@@ -7,6 +7,7 @@
7
7
  */
8
8
  import { Worker } from 'node:worker_threads';
9
9
  import { createLogger } from '@bendyline/gezel';
10
+ import { disableOnnxRuntimeTelemetry } from '@bendyline/gezel/native';
10
11
  import { findServiceWorkerEntry } from '../utils/service-worker-entry.js';
11
12
  import { createInProcessCatalogHost } from './catalog-host.js';
12
13
  const log = createLogger('knowledge');
@@ -63,6 +64,7 @@ export function createWorkerCatalogHost() {
63
64
  return null;
64
65
  }
65
66
  try {
67
+ disableOnnxRuntimeTelemetry();
66
68
  const w = new Worker(entry);
67
69
  w.on('message', (msg) => {
68
70
  const p = pending.get(msg.id);
@@ -1,12 +1,21 @@
1
+ import { innermostErrorReason } from '@bendyline/gezel';
1
2
  import { IdentityResponseSchema, certFingerprintFromPem, fingerprintOfPublicKeyPem, verifyIdentitySignature, } from '../remotes/identity.js';
2
3
  import { createPinnedFetch } from '../remotes/pinned-fetch.js';
3
4
  const MACHINE_REMOTE_ID = 'this-machine';
4
5
  export async function inspectMachineRuntime(runtime) {
5
6
  const fetchImpl = runtime.cert ? createPinnedFetch(runtime.cert) : fetch;
6
7
  try {
7
- const response = await fetchImpl(`${runtime.baseUrl}/v1/identity`, {
8
- signal: AbortSignal.timeout(5_000),
9
- });
8
+ let response;
9
+ try {
10
+ response = await fetchImpl(`${runtime.baseUrl}/v1/identity`, {
11
+ signal: AbortSignal.timeout(5_000),
12
+ });
13
+ }
14
+ catch (error) {
15
+ // Bare, this surfaced as `fetch failed` in chat bubbles that never
16
+ // mentioned a machine engine at all.
17
+ throw new Error(`Gezel's machine engine at ${runtime.baseUrl} isn't answering (${innermostErrorReason(error)})`, { cause: error });
18
+ }
10
19
  if (!response.ok)
11
20
  throw new Error(`identity returned HTTP ${response.status}`);
12
21
  const identity = IdentityResponseSchema.parse(await response.json());
@@ -15,6 +15,7 @@
15
15
  import { createHash } from 'node:crypto';
16
16
  import { createLogger, embeddingProfileArtifacts, } from '@bendyline/gezel';
17
17
  import { EmbedderUnavailableError, KNOWLEDGE_EMBEDDING_PROFILES, createProfileEmbedder, resolveTransformersModelOptions, verifyProfileArtifacts, } from '@bendyline/gezel-knowledge';
18
+ import { disableOnnxRuntimeTelemetry } from '@bendyline/gezel/native';
18
19
  import { HF_CACHE_DIR_ENV, ModelFileDownloadError, TRANSFORMERS_MODULE, ensureVerifiedModelFile, isCorruptTransformersCacheFailure, isMissingModule, loadTransformersModelWithCacheRecovery, pinTransformersCacheDir, } from '../transformers-cache.js';
19
20
  export const log = createLogger('memory');
20
21
  /**
@@ -236,6 +237,7 @@ export async function loadPipeline() {
236
237
  // and throws "Unable to get model file path or buffer". The dir is
237
238
  // passed via env so this works in the embed worker thread too, which
238
239
  // inherits `process.env` but has no other view of the gezel home.
240
+ disableOnnxRuntimeTelemetry();
239
241
  const cacheDir = process.env[HF_CACHE_DIR_ENV];
240
242
  if (cacheDir)
241
243
  await pinTransformersCacheDir(cacheDir);
@@ -348,6 +350,7 @@ async function verifyDaemonPipeline() {
348
350
  }
349
351
  }
350
352
  async function defaultTransformersCacheDir() {
353
+ disableOnnxRuntimeTelemetry();
351
354
  const { env } = await import('@huggingface/transformers');
352
355
  // 4.x types it nullable: null only where there is no filesystem cache.
353
356
  return env.cacheDir ?? '';
@@ -19,6 +19,7 @@
19
19
  */
20
20
  import { Worker } from 'node:worker_threads';
21
21
  import { createLogger, sameVectorSpace } from '@bendyline/gezel';
22
+ import { disableOnnxRuntimeTelemetry } from '@bendyline/gezel/native';
22
23
  import { retireModelWorker } from '../utils/retire-model-worker.js';
23
24
  import { findServiceWorkerEntry } from '../utils/service-worker-entry.js';
24
25
  import { ModelWorkCancelledError, PipelineLoadError, cancelInFlightModelWork, daemonEmbedderPin, daemonEmbedderVerified, passageInstruction, queryInstruction, runEmbed, runProfileQueryEmbed, } from './embed-core.js';
@@ -124,6 +125,7 @@ function ensureWorker() {
124
125
  return null;
125
126
  }
126
127
  try {
128
+ disableOnnxRuntimeTelemetry();
127
129
  const w = new Worker(entry);
128
130
  w.on('message', onMessage);
129
131
  w.on('error', (err) => onWorkerDown(err instanceof Error ? err.message : String(err)));
@@ -11,12 +11,12 @@
11
11
  * The video half lives with the video engine in the service, which shares
12
12
  * the helpers here.
13
13
  */
14
- import { totalmem } from 'node:os';
15
14
  import { MEDIA_MODEL_WONT_RUN_CODE, assessMediaFit, createLogger, imageMemoryRequirement, imageModelDefaultSide, resolveImageSize, } from '@bendyline/gezel';
16
15
  import { isCudaArchivePath } from '@bendyline/gezel/native';
17
16
  import { sdServerLoadProfile } from '../providers/image/sd-cpp.js';
18
17
  import { configuredEngineMemoryBytes, staticCapacityCeilings, } from '../providers/native/device-capacity.js';
19
18
  import { measuredCapacityBudget } from '../providers/native/measured-budget.js';
19
+ import { totalMemoryBytes } from '../system/host-memory.js';
20
20
  import { detectMemoryProfileCached } from '../system/memory.js';
21
21
  import { imageCatalogWeightFiles, sdCppMainComponent } from './weight-facts.js';
22
22
  const log = createLogger('media-fit');
@@ -63,7 +63,7 @@ export function imageMediaPool(kind, host = {
63
63
  /** The ceilings the device ledger applies on this machine, automatic and under the person's limit. */
64
64
  export async function mediaCapacity(pool, readConfig) {
65
65
  const budget = await measuredCapacityBudget();
66
- const ram = totalmem();
66
+ const ram = totalMemoryBytes();
67
67
  const auto = staticCapacityCeilings(budget, ram);
68
68
  const configured = configuredEngineMemoryBytes(await readConfig().catch(() => ({})));
69
69
  const limited = configured === undefined ? undefined : staticCapacityCeilings(budget, ram, configured);
@@ -34,3 +34,8 @@ export declare function localSdCppWeightFiles(model: {
34
34
  path: string;
35
35
  }>;
36
36
  }): Promise<MediaWeightFile[]>;
37
+ /** Weight files on disk, priced from their headers where they are safetensors. */
38
+ export declare function localWeightFiles(entries: ReadonlyArray<{
39
+ component: string;
40
+ path: string;
41
+ }>): Promise<MediaWeightFile[]>;
@@ -148,10 +148,13 @@ export function imageCatalogWeightFiles(manifest) {
148
148
  }
149
149
  /** The weight files of an installed sd-cpp model, headers read from disk. */
150
150
  export async function localSdCppWeightFiles(model) {
151
- const entries = [
151
+ return localWeightFiles([
152
152
  { component: sdCppMainComponent(model.weightsKind), path: model.weightsPath },
153
153
  ...model.auxiliaryFiles.map((a) => ({ component: a.role, path: a.path })),
154
- ];
154
+ ]);
155
+ }
156
+ /** Weight files on disk, priced from their headers where they are safetensors. */
157
+ export async function localWeightFiles(entries) {
155
158
  const out = [];
156
159
  for (const entry of entries) {
157
160
  const format = weightFileFormat(entry.path);
@@ -123,7 +123,7 @@ export async function buildDs4Provider(opts) {
123
123
  // This tier assumes DeepSeek-V4's small resident footprint. A model whose
124
124
  // non-routed weights are much larger caps it further via the catalog's
125
125
  // `ds4.maxLaunchCtx`; see `resolveDs4LaunchCtx` below.
126
- const totalRamBytes = (await import('node:os')).totalmem();
126
+ const totalRamBytes = (await import('../../system/host-memory.js')).totalMemoryBytes();
127
127
  const totalRamGb = totalRamBytes / 1024 ** 3;
128
128
  const ramTieredCtx = ds4RamTieredContext(totalRamBytes);
129
129
  const ds4ConstrainedToolNoSignalMs = (() => {
@@ -1,5 +1,5 @@
1
- import { totalmem } from 'node:os';
2
1
  import { DS4_FULL_RESIDENCY_HEADROOM_BYTES, LLAMA_CPP_VISION_COMPUTE_BYTES, LLAMA_CPP_WEIGHTS_MULTIPLIER, ds4FitsFullResidency, } from '@bendyline/gezel';
2
+ import { totalMemoryBytes } from '../../system/host-memory.js';
3
3
  const GB = 1024 ** 3;
4
4
  export { DS4_FULL_RESIDENCY_HEADROOM_BYTES };
5
5
  /**
@@ -43,7 +43,7 @@ export function ds4BaseResidentBytes(opts) {
43
43
  export function canUseDs4FullResidency(opts = {}) {
44
44
  return ds4FitsFullResidency({
45
45
  modelSizeBytes: opts.modelSizeBytes,
46
- totalRamBytes: opts.totalRamBytes ?? totalmem(),
46
+ totalRamBytes: opts.totalRamBytes ?? totalMemoryBytes(),
47
47
  companionBytes: opts.companionBytes,
48
48
  platform: opts.platform ?? process.platform,
49
49
  arch: opts.arch ?? process.arch,
@@ -82,7 +82,7 @@ export function shouldUseDs4SsdStreaming(opts = {}) {
82
82
  * state + the cache cannot consume the runtime/OS reserve.
83
83
  */
84
84
  export function planDs4ExpertCache(opts) {
85
- const totalRamBytes = opts.totalRamBytes ?? totalmem();
85
+ const totalRamBytes = opts.totalRamBytes ?? totalMemoryBytes();
86
86
  const catalogCacheBytes = opts.catalogCacheBytes ?? 0;
87
87
  const fixedResidentBytes = Math.max(0, (opts.catalogResidentBytes ?? 0) - catalogCacheBytes);
88
88
  const fallbackGb = totalRamBytes >= 120 * GB ? 64 : totalRamBytes >= 88 * GB ? 48 : 32;
@@ -1,5 +1,7 @@
1
1
  /**
2
- * Per-model img2img capability resolution for the local sd-cpp engine.
2
+ * Per-model image-editing capability for the local sd-cpp engine: whether a
3
+ * model re-noises a source (img2img), reads sources as references beside an
4
+ * instruction, or cannot use one. The img2img half first.
3
5
  *
4
6
  * stable-diffusion.cpp's `/sdapi/v1/img2img` endpoint accepts
5
7
  * `init_images` for any loaded model, but whether the *architecture*
@@ -37,4 +39,37 @@ export interface Img2ImgSupportVerdict {
37
39
  /** Human-readable reason, set only when unsupported. */
38
40
  reason?: string;
39
41
  }
42
+ /**
43
+ * Models that edit by reading source pictures as references beside an
44
+ * instruction (sd-server's `extra_images`), rather than re-noising one. The
45
+ * same ladder as above: a catalog `referenceImages` declaration persisted at
46
+ * pull time wins, then this map, which is how installs that predate the
47
+ * field learn it.
48
+ *
49
+ * FLUX.2 Klein: verified on the pinned engine (native sd-cpp master-975) on
50
+ * an RTX Spark, 2026-10-27, recoloring, adding an object and composing two
51
+ * sources at 4 steps in 12 s.
52
+ */
53
+ export declare const MODEL_REFERENCE_EDIT_SUPPORT: Map<string, {
54
+ maxImages: number;
55
+ editOnly?: boolean;
56
+ }>;
57
+ export interface ImageEditCapabilityInput extends Img2ImgSupportInput {
58
+ /** Explicit `referenceImages` from catalog/installed metadata. */
59
+ referenceImages?: {
60
+ max: number;
61
+ } | undefined;
62
+ /** Explicit `editOnly` from catalog/installed metadata. */
63
+ editOnly?: boolean | undefined;
64
+ }
65
+ export interface ImageEditCapability {
66
+ /** Absent when the model cannot use a source picture at all. */
67
+ editMode?: 'reference' | 'img2img';
68
+ /** Sources one edit takes: the reference limit, or 1 for img2img. */
69
+ maxSourceImages?: number;
70
+ editOnly: boolean;
71
+ /** Why the model cannot edit; set only when `editMode` is absent. */
72
+ reason?: string;
73
+ }
74
+ export declare function resolveImageEditCapability(input: ImageEditCapabilityInput): ImageEditCapability;
40
75
  export declare function resolveImg2ImgSupport(input: Img2ImgSupportInput): Img2ImgSupportVerdict;
@@ -1,5 +1,7 @@
1
1
  /**
2
- * Per-model img2img capability resolution for the local sd-cpp engine.
2
+ * Per-model image-editing capability for the local sd-cpp engine: whether a
3
+ * model re-noises a source (img2img), reads sources as references beside an
4
+ * instruction, or cannot use one. The img2img half first.
3
5
  *
4
6
  * stable-diffusion.cpp's `/sdapi/v1/img2img` endpoint accepts
5
7
  * `init_images` for any loaded model, but whether the *architecture*
@@ -44,6 +46,33 @@ export const MODEL_IMG2IMG_SUPPORT = new Map([
44
46
  ['krea-2-turbo-q6', false],
45
47
  ['krea-2-turbo-q8', false],
46
48
  ]);
49
+ /**
50
+ * Models that edit by reading source pictures as references beside an
51
+ * instruction (sd-server's `extra_images`), rather than re-noising one. The
52
+ * same ladder as above: a catalog `referenceImages` declaration persisted at
53
+ * pull time wins, then this map, which is how installs that predate the
54
+ * field learn it.
55
+ *
56
+ * FLUX.2 Klein: verified on the pinned engine (native sd-cpp master-975) on
57
+ * an RTX Spark, 2026-10-27, recoloring, adding an object and composing two
58
+ * sources at 4 steps in 12 s.
59
+ */
60
+ export const MODEL_REFERENCE_EDIT_SUPPORT = new Map([
61
+ ['flux-2-klein-4b-q4', { maxImages: 3 }],
62
+ ['qwen-image-edit-2511-q4', { maxImages: 3, editOnly: true }],
63
+ ]);
64
+ export function resolveImageEditCapability(input) {
65
+ const assessed = input.modelId ? MODEL_REFERENCE_EDIT_SUPPORT.get(input.modelId) : undefined;
66
+ const editOnly = input.editOnly ?? assessed?.editOnly ?? false;
67
+ const maxImages = input.referenceImages?.max ?? assessed?.maxImages;
68
+ if (maxImages !== undefined) {
69
+ return { editMode: 'reference', maxSourceImages: maxImages, editOnly };
70
+ }
71
+ const img2img = resolveImg2ImgSupport(input);
72
+ if (img2img.supported)
73
+ return { editMode: 'img2img', maxSourceImages: 1, editOnly };
74
+ return { editOnly, ...(img2img.reason ? { reason: img2img.reason } : {}) };
75
+ }
47
76
  export function resolveImg2ImgSupport(input) {
48
77
  if (input.explicit !== undefined) {
49
78
  return input.explicit
@@ -21,6 +21,7 @@ export interface LocalImageEngineOptions {
21
21
  export declare class LocalImageEngine {
22
22
  private readonly opts;
23
23
  readonly pulls: ImageModelPullRegistry;
24
+ readonly loraPulls: ImageModelPullRegistry;
24
25
  private readonly lifecycle;
25
26
  constructor(opts: LocalImageEngineOptions);
26
27
  /** Whether this host can render at all: an engine binary, a server URL, or the mock. */
@@ -1,6 +1,6 @@
1
1
  import { ProviderLifecycle } from '../provider-lifecycle.js';
2
2
  import { createLocalImageProvider } from './local-factory.js';
3
- import { ImageModelPullRegistry } from './pull-registry.js';
3
+ import { ImageModelPullRegistry, imageLoraPullResolver } from './pull-registry.js';
4
4
  /**
5
5
  * Hosted inference's image engine: the local sd-server provider, built on
6
6
  * first use, plus the pulls that install its models. Reset drains in-flight
@@ -9,12 +9,17 @@ import { ImageModelPullRegistry } from './pull-registry.js';
9
9
  export class LocalImageEngine {
10
10
  opts;
11
11
  pulls;
12
+ loraPulls;
12
13
  // Only promise-returning operations can be tracked; a pull is an async
13
14
  // iterable and is cancelled through its own registry instead.
14
- lifecycle = new ProviderLifecycle(new Set(['generate', 'deleteModel']));
15
+ lifecycle = new ProviderLifecycle(new Set(['generate', 'deleteModel', 'deleteLora']));
15
16
  constructor(opts) {
16
17
  this.opts = opts;
17
18
  this.pulls = new ImageModelPullRegistry({ imageProvider: this, catalog: opts.catalog });
19
+ this.loraPulls = new ImageModelPullRegistry({
20
+ resolve: imageLoraPullResolver(this, opts.catalog),
21
+ kind: 'image-lora',
22
+ });
18
23
  }
19
24
  /** Whether this host can render at all: an engine binary, a server URL, or the mock. */
20
25
  available() {
@@ -42,6 +47,7 @@ export class LocalImageEngine {
42
47
  }
43
48
  async shutdown() {
44
49
  this.pulls.clear();
50
+ this.loraPulls.clear();
45
51
  await this.lifecycle.reset();
46
52
  }
47
53
  }
@@ -15,6 +15,7 @@
15
15
  * base URL (127.0.0.1:9081) and no supervisor. `health()` reports
16
16
  * `not-configured` so the UI can say the engine is not installed.
17
17
  */
18
+ import type { MediaWeightFile } from '@bendyline/gezel';
18
19
  import type { GpuArbiter } from '../gpu-arbiter.js';
19
20
  import type { NativeCapacityOptions } from '../native/device-capacity.js';
20
21
  import type { ImageProvider, InstalledImageModelInfo } from './types.js';
@@ -92,4 +93,12 @@ export interface ResolvedInstalledModel extends InstalledImageModelInfo {
92
93
  * encoder runs once per image, so GPUs lose nothing. `--diffusion-fa` means
93
94
  * `--flash-attn off,diffusion=on`, so that branch already leaves it off.
94
95
  */
95
- export declare function buildSdServerArgs(model: ResolvedInstalledModel, port: number): string[];
96
+ export declare function buildSdServerArgs(model: ResolvedInstalledModel, port: number, opts?: {
97
+ loraDir?: string;
98
+ }): string[];
99
+ /**
100
+ * The LoRA files a render of `modelId` can hold at once (its fast mode and
101
+ * its largest style), priced like weights. Read when the engine launches, so
102
+ * a LoRA installed later counts from the next launch.
103
+ */
104
+ export declare function launchedLoraWeightFiles(loraRoot: string, modelId: string): Promise<MediaWeightFile[]>;
@@ -15,18 +15,18 @@
15
15
  * base URL (127.0.0.1:9081) and no supervisor. `health()` reports
16
16
  * `not-configured` so the UI can say the engine is not installed.
17
17
  */
18
- import { readFile } from 'node:fs/promises';
18
+ import { mkdir, readFile } from 'node:fs/promises';
19
19
  import { join } from 'node:path';
20
20
  import { imageMediaPool, sdServerLedgerClaim, sdServerRequirement, } from '../../models/image-fit.js';
21
21
  import { findModelRoot, listOverlayModelIds, modelStorageRoots, verifyReadOnlyModelPayload, } from '../../models/storage-roots.js';
22
- import { localSdCppWeightFiles } from '../../models/weight-facts.js';
22
+ import { localSdCppWeightFiles, localWeightFiles } from '../../models/weight-facts.js';
23
23
  import { pressureIdleGraceMs } from '../native/capacity-broker.js';
24
24
  import { measuredCapacityBudget } from '../native/measured-budget.js';
25
25
  import { pickFreePort } from '../native/port.js';
26
26
  import { NativeEngineSupervisor } from '../native/supervisor.js';
27
27
  import { patientFetch } from '../patient-fetch.js';
28
28
  import { MockImageProvider } from './mock.js';
29
- import { MODEL_SAMPLE_STEP_DEFAULTS, StableDiffusionCppProvider, sdServerLoadProfile, } from './sd-cpp.js';
29
+ import { MODEL_SAMPLE_STEP_DEFAULTS, StableDiffusionCppProvider, readInstalledLoras, sdServerLoadProfile, } from './sd-cpp.js';
30
30
  /**
31
31
  * Nothing to generate with: the person has to download a model first. Its
32
32
  * own type so a route can answer with the fix, not a bare 500, which reads
@@ -80,6 +80,8 @@ export async function createLocalImageProvider(opts) {
80
80
  // when it changes; resolveLaunch reads it on the next start.
81
81
  const launchState = { modelId: undefined };
82
82
  let launchedModel;
83
+ const loraRoots = modelStorageRoots({ home: opts.home, engine: 'sd-cpp-loras', env });
84
+ const loraDir = loraRoots.writableRoot;
83
85
  const supervisor = new NativeEngineSupervisor({
84
86
  capacity: {
85
87
  ...sdServerCapacity(opts.home),
@@ -93,7 +95,10 @@ export async function createLocalImageProvider(opts) {
93
95
  const requirement = sdServerRequirement({
94
96
  modelId: launchedModel.id,
95
97
  weightsKind: launchedModel.weightsKind,
96
- files: await localSdCppWeightFiles(launchedModel),
98
+ files: [
99
+ ...(await localSdCppWeightFiles(launchedModel)),
100
+ ...(await launchedLoraWeightFiles(loraDir, launchedModel.id)),
101
+ ],
97
102
  pool: imageMediaPool(budget.kind),
98
103
  });
99
104
  return sdServerLedgerClaim(requirement, budget.fastBytes);
@@ -131,9 +136,11 @@ export async function createLocalImageProvider(opts) {
131
136
  if (!model)
132
137
  throw new NoLocalImageModelError();
133
138
  launchedModel = model;
139
+ // sd-server ignores a LoRA folder that does not exist yet, then lists none.
140
+ await mkdir(loraDir, { recursive: true });
134
141
  return {
135
142
  command: binary,
136
- args: buildSdServerArgs(model, port),
143
+ args: buildSdServerArgs(model, port, { loraDir }),
137
144
  baseUrl: `http://127.0.0.1:${port}`,
138
145
  };
139
146
  },
@@ -146,6 +153,7 @@ export async function createLocalImageProvider(opts) {
146
153
  storageRoots,
147
154
  supervisor,
148
155
  launchState,
156
+ loraRoots,
149
157
  fetchImpl: patientFetch(),
150
158
  ...(defaultLocalModelId ? { defaultModelId: defaultLocalModelId } : {}),
151
159
  ...(opts.arbiter ? { arbiter: opts.arbiter } : {}),
@@ -239,7 +247,7 @@ async function findFirstInstalledModel(storageRoots) {
239
247
  * encoder runs once per image, so GPUs lose nothing. `--diffusion-fa` means
240
248
  * `--flash-attn off,diffusion=on`, so that branch already leaves it off.
241
249
  */
242
- export function buildSdServerArgs(model, port) {
250
+ export function buildSdServerArgs(model, port, opts = {}) {
243
251
  const args = [];
244
252
  if (model.weightsKind === 'diffusion-model') {
245
253
  args.push('--diffusion-model', model.weightsPath);
@@ -265,6 +273,25 @@ export function buildSdServerArgs(model, port) {
265
273
  args.push('--vae-tiling');
266
274
  if (profile.stepCache)
267
275
  args.push('--cache-mode', profile.stepCache);
276
+ if (profile.modelArgs)
277
+ args.push('--model-args', profile.modelArgs);
278
+ // At runtime for every model: a LoRA then changes per request without
279
+ // touching the weights. In auto mode an unquantized checkpoint (SD, SDXL)
280
+ // would merge it into the weights instead.
281
+ if (opts.loraDir)
282
+ args.push('--lora-model-dir', opts.loraDir, '--lora-apply-mode', 'at_runtime');
268
283
  args.push('--listen-ip', '127.0.0.1', '--listen-port', String(port));
269
284
  return args;
270
285
  }
286
+ /**
287
+ * The LoRA files a render of `modelId` can hold at once (its fast mode and
288
+ * its largest style), priced like weights. Read when the engine launches, so
289
+ * a LoRA installed later counts from the next launch.
290
+ */
291
+ export async function launchedLoraWeightFiles(loraRoot, modelId) {
292
+ const fitting = (await readInstalledLoras(loraRoot)).filter((l) => l.baseModels.includes(modelId));
293
+ const largest = (purpose) => fitting
294
+ .filter((l) => l.purpose === purpose)
295
+ .sort((a, b) => b.approxSizeBytes - a.approxSizeBytes)[0];
296
+ return localWeightFiles([largest('speed'), largest('style')].flatMap((l) => l ? [{ component: 'lora', path: join(loraRoot, l.id, l.filename) }] : []));
297
+ }
@@ -4,19 +4,26 @@
4
4
  * colour is derived from a hash of the prompt so different prompts yield
5
5
  * visually different (but still trivially small) outputs.
6
6
  */
7
- import type { ImageEngineHealth, ImageGenerationInput, ImageGenerationOutput, ImageModelPullEvent, ImageModelPullSpec, ImageProvider, InstalledImageModelInfo } from './types.js';
7
+ import type { ImageEngineHealth, ImageGenerationInput, ImageGenerationOutput, ImageLoraPullSpec, ImageModelPullEvent, ImageModelPullSpec, ImageProvider, InstalledImageLoraInfo, InstalledImageModelInfo } from './types.js';
8
8
  export declare class MockImageProvider implements ImageProvider {
9
9
  readonly name = "mock";
10
10
  private readonly installed;
11
+ private readonly loras;
11
12
  /** Internal counter driving deterministic seed generation on "random" seeds. */
12
13
  private nextSeed;
13
14
  constructor(opts?: {
14
15
  preinstalled?: InstalledImageModelInfo[];
16
+ loras?: InstalledImageLoraInfo[];
15
17
  });
16
18
  /** Number of input images received on the most recent generate(). Tests use this. */
17
19
  lastInputImageCount: number;
20
+ /** The most recent generate() input. Tests use this. */
21
+ lastInput: ImageGenerationInput | undefined;
18
22
  generate(input: ImageGenerationInput): Promise<ImageGenerationOutput>;
19
23
  listInstalledModels(): Promise<InstalledImageModelInfo[]>;
24
+ listLoras(): Promise<InstalledImageLoraInfo[]>;
25
+ pullLora(id: string, spec: ImageLoraPullSpec): AsyncIterable<ImageModelPullEvent>;
26
+ deleteLora(id: string): Promise<void>;
20
27
  pullModel(id: string, spec: ImageModelPullSpec, signal?: AbortSignal): AsyncIterable<ImageModelPullEvent>;
21
28
  deleteModel(id: string): Promise<void>;
22
29
  health(): Promise<ImageEngineHealth>;