@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
@@ -8,22 +8,33 @@ import { deflateSync } from 'node:zlib';
8
8
  export class MockImageProvider {
9
9
  name = 'mock';
10
10
  installed = new Map();
11
+ loras = new Map();
11
12
  /** Internal counter driving deterministic seed generation on "random" seeds. */
12
13
  nextSeed = 1;
13
14
  constructor(opts = {}) {
14
15
  for (const m of opts.preinstalled ?? []) {
15
16
  this.installed.set(m.id, m);
16
17
  }
18
+ for (const l of opts.loras ?? [])
19
+ this.loras.set(l.id, l);
17
20
  }
18
21
  /** Number of input images received on the most recent generate(). Tests use this. */
19
22
  lastInputImageCount = 0;
23
+ /** The most recent generate() input. Tests use this. */
24
+ lastInput;
20
25
  async generate(input) {
21
26
  this.lastInputImageCount = input.inputImages?.length ?? 0;
27
+ this.lastInput = input;
22
28
  const width = clamp(input.width ?? 16, 1, 512);
23
29
  const height = clamp(input.height ?? 16, 1, 512);
24
30
  const steps = clamp(input.steps ?? 1, 1, 100);
25
31
  const seed = input.seed ?? this.nextSeed++;
26
32
  const model = input.model ?? [...this.installed.keys()][0] ?? 'mock-model';
33
+ // Applied as the real engine applies them: installed, and fitting the model.
34
+ const loras = (input.loras ?? []).flatMap(({ id }) => {
35
+ const lora = this.loras.get(id);
36
+ return lora?.baseModels.includes(model) ? [{ id: lora.id, name: lora.name }] : [];
37
+ });
27
38
  const rgba = solidFill(width, height, colourForPrompt(input.prompt));
28
39
  const png = encodePng(rgba, width, height);
29
40
  return {
@@ -35,11 +46,45 @@ export class MockImageProvider {
35
46
  widthPx: width,
36
47
  heightPx: height,
37
48
  durationMs: 1,
49
+ ...(loras.length > 0 ? { loras } : {}),
38
50
  },
39
51
  };
40
52
  }
41
53
  async listInstalledModels() {
42
- return [...this.installed.values()];
54
+ return [...this.installed.values()].map((m) => {
55
+ const loras = [...this.loras.values()]
56
+ .filter((l) => l.baseModels.includes(m.id))
57
+ .map(({ id, name, purpose, steps }) => ({
58
+ id,
59
+ name,
60
+ purpose,
61
+ ...(steps !== undefined ? { steps } : {}),
62
+ }));
63
+ return loras.length > 0 ? { ...m, loras } : m;
64
+ });
65
+ }
66
+ async listLoras() {
67
+ return [...this.loras.values()];
68
+ }
69
+ async *pullLora(id, spec) {
70
+ yield {
71
+ type: 'progress',
72
+ bytesWritten: spec.approxSizeBytes,
73
+ totalBytes: spec.approxSizeBytes,
74
+ };
75
+ this.loras.set(id, {
76
+ id,
77
+ name: spec.name,
78
+ purpose: spec.purpose,
79
+ ...(spec.render?.steps !== undefined ? { steps: spec.render.steps } : {}),
80
+ baseModels: spec.baseModels,
81
+ approxSizeBytes: spec.approxSizeBytes,
82
+ installedAt: new Date().toISOString(),
83
+ });
84
+ yield { type: 'done', id };
85
+ }
86
+ async deleteLora(id) {
87
+ this.loras.delete(id);
43
88
  }
44
89
  async *pullModel(id, spec, signal) {
45
90
  const total = spec.approxSizeBytes;
@@ -34,6 +34,13 @@ type Listener = (event: ImageModelPullEvent) => void;
34
34
  export interface ImageProviderSource {
35
35
  current(): Promise<Pick<ImageProvider, 'pullModel'>>;
36
36
  }
37
+ /** One download the registry runs: its expected size and the event stream. */
38
+ export interface ImagePullJob {
39
+ totalBytes: number;
40
+ run(signal: AbortSignal): AsyncIterable<ImageModelPullEvent>;
41
+ }
42
+ /** Turns a catalog id into a pull, or `null` for an id the catalog lacks. */
43
+ export type ImagePullResolver = (id: string) => Promise<ImagePullJob | null>;
37
44
  /**
38
45
  * Owns the lifecycle of every in-flight image-model pull.
39
46
  *
@@ -52,11 +59,18 @@ export interface ImageProviderSource {
52
59
  export declare class ImageModelPullRegistry {
53
60
  private readonly entries;
54
61
  private readonly controllers;
55
- private readonly imageProvider;
56
- private readonly catalog;
62
+ private readonly resolve;
63
+ private readonly kind;
64
+ /**
65
+ * Image models by default; `resolve` makes it a registry for another kind
66
+ * of download (LoRAs), whose ids then never share keys with models.
67
+ */
57
68
  constructor(opts: {
58
69
  imageProvider: ImageProviderSource;
59
70
  catalog: CatalogService;
71
+ } | {
72
+ resolve: ImagePullResolver;
73
+ kind: string;
60
74
  });
61
75
  /**
62
76
  * Start a pull for `id`. Idempotent — when a pull is already in flight
@@ -101,6 +115,14 @@ export declare class ImageModelPullRegistry {
101
115
  }
102
116
  export declare class UnknownImageModelError extends Error {
103
117
  readonly id: string;
104
- constructor(id: string);
118
+ constructor(id: string, kind?: string);
105
119
  }
120
+ /**
121
+ * LoRA pulls: the catalog's `image-lora` entries, installed by whichever
122
+ * provider owns the engine. A provider without LoRA support fails the pull
123
+ * with a plain message.
124
+ */
125
+ export declare function imageLoraPullResolver(imageProvider: {
126
+ current(): Promise<Pick<ImageProvider, 'pullLora'>>;
127
+ }, catalog: CatalogService): ImagePullResolver;
106
128
  export {};
@@ -25,11 +25,21 @@ const FINISHED_TTL_MS = 8_000;
25
25
  export class ImageModelPullRegistry {
26
26
  entries = new Map();
27
27
  controllers = new Map();
28
- imageProvider;
29
- catalog;
28
+ resolve;
29
+ kind;
30
+ /**
31
+ * Image models by default; `resolve` makes it a registry for another kind
32
+ * of download (LoRAs), whose ids then never share keys with models.
33
+ */
30
34
  constructor(opts) {
31
- this.imageProvider = opts.imageProvider;
32
- this.catalog = opts.catalog;
35
+ if ('resolve' in opts) {
36
+ this.resolve = opts.resolve;
37
+ this.kind = opts.kind;
38
+ }
39
+ else {
40
+ this.resolve = imageModelPullResolver(opts.imageProvider, opts.catalog);
41
+ this.kind = 'image-model';
42
+ }
33
43
  }
34
44
  /**
35
45
  * Start a pull for `id`. Idempotent — when a pull is already in flight
@@ -47,40 +57,21 @@ export class ImageModelPullRegistry {
47
57
  }
48
58
  // Resolve the catalog entry up-front so we can fail fast with a
49
59
  // proper 404 rather than emitting a 200 + error SSE for "unknown id".
50
- const item = await this.catalog.get('image-model', id);
51
- if (!item || item.manifest.kind !== 'image-model') {
52
- throw new UnknownImageModelError(id);
53
- }
54
- const manifest = item.manifest;
60
+ const job = await this.resolve(id);
61
+ if (!job)
62
+ throw new UnknownImageModelError(id, this.kind);
55
63
  const snapshot = {
56
64
  id,
57
65
  startedAt: new Date().toISOString(),
58
66
  bytesWritten: 0,
59
- totalBytes: manifest.approxSizeBytes +
60
- manifest.auxiliaryFiles.reduce((n, f) => n + f.approxSizeBytes, 0),
67
+ totalBytes: job.totalBytes,
61
68
  finished: false,
62
69
  };
63
70
  const entry = { snapshot, listeners: new Set() };
64
71
  this.entries.set(id, entry);
65
72
  const controller = new AbortController();
66
73
  this.controllers.set(id, controller);
67
- const spec = {
68
- downloadUrl: manifest.downloadUrl,
69
- sha256: manifest.sha256,
70
- approxSizeBytes: manifest.approxSizeBytes,
71
- name: manifest.name,
72
- weightsKind: manifest.weightsKind,
73
- ...(manifest.supportsImg2Img !== undefined
74
- ? { supportsImg2Img: manifest.supportsImg2Img }
75
- : {}),
76
- auxiliaryFiles: manifest.auxiliaryFiles.map((aux) => ({
77
- role: aux.role,
78
- downloadUrl: aux.downloadUrl,
79
- sha256: aux.sha256,
80
- approxSizeBytes: aux.approxSizeBytes,
81
- })),
82
- };
83
- void this.consume(id, spec, controller.signal);
74
+ void this.consume(id, job, controller.signal);
84
75
  return { snapshot: { ...snapshot }, alreadyRunning: false };
85
76
  }
86
77
  list() {
@@ -148,13 +139,12 @@ export class ImageModelPullRegistry {
148
139
  if (snapshot.finished && !snapshot.error)
149
140
  listener({ type: 'done', id: snapshot.id });
150
141
  }
151
- async consume(id, spec, signal) {
142
+ async consume(id, job, signal) {
152
143
  const entry = this.entries.get(id);
153
144
  if (!entry)
154
145
  return;
155
146
  try {
156
- const provider = await this.imageProvider.current();
157
- for await (const event of provider.pullModel(id, spec, signal)) {
147
+ for await (const event of job.run(signal)) {
158
148
  this.applyEvent(entry, event);
159
149
  this.broadcast(entry, event);
160
150
  if (event.type === 'done' || event.type === 'error')
@@ -171,7 +161,8 @@ export class ImageModelPullRegistry {
171
161
  finally {
172
162
  entry.snapshot.finished = true;
173
163
  // Drop the controller — cancel() should be a no-op once finished.
174
- this.controllers.delete(id);
164
+ if (this.entries.get(id) === entry)
165
+ this.controllers.delete(id);
175
166
  // Keep the snapshot around briefly so a client mid-reconnect still
176
167
  // sees the terminal state on `list()` / `subscribe()`. Then garbage
177
168
  // collect.
@@ -184,6 +175,9 @@ export class ImageModelPullRegistry {
184
175
  }
185
176
  applyEvent(entry, event) {
186
177
  const s = entry.snapshot;
178
+ // Publish terminal state before generator cleanup can yield to a new subscriber.
179
+ if (event.type === 'done' || event.type === 'error')
180
+ s.finished = true;
187
181
  if (event.type === 'progress') {
188
182
  s.bytesWritten = event.bytesWritten;
189
183
  if (event.totalBytes !== undefined)
@@ -219,9 +213,77 @@ export class ImageModelPullRegistry {
219
213
  }
220
214
  export class UnknownImageModelError extends Error {
221
215
  id;
222
- constructor(id) {
223
- super(`unknown image-model: ${id}`);
216
+ constructor(id, kind = 'image-model') {
217
+ super(`unknown ${kind}: ${id}`);
224
218
  this.id = id;
225
219
  this.name = 'UnknownImageModelError';
226
220
  }
227
221
  }
222
+ function imageModelPullResolver(imageProvider, catalog) {
223
+ return async (id) => {
224
+ const item = await catalog.get('image-model', id);
225
+ if (!item || item.manifest.kind !== 'image-model')
226
+ return null;
227
+ const manifest = item.manifest;
228
+ const spec = {
229
+ downloadUrl: manifest.downloadUrl,
230
+ sha256: manifest.sha256,
231
+ approxSizeBytes: manifest.approxSizeBytes,
232
+ name: manifest.name,
233
+ weightsKind: manifest.weightsKind,
234
+ ...(manifest.supportsImg2Img !== undefined
235
+ ? { supportsImg2Img: manifest.supportsImg2Img }
236
+ : {}),
237
+ ...(manifest.referenceImages ? { referenceImages: manifest.referenceImages } : {}),
238
+ ...(manifest.editOnly !== undefined ? { editOnly: manifest.editOnly } : {}),
239
+ auxiliaryFiles: manifest.auxiliaryFiles.map((aux) => ({
240
+ role: aux.role,
241
+ downloadUrl: aux.downloadUrl,
242
+ sha256: aux.sha256,
243
+ approxSizeBytes: aux.approxSizeBytes,
244
+ })),
245
+ };
246
+ return {
247
+ totalBytes: manifest.approxSizeBytes +
248
+ manifest.auxiliaryFiles.reduce((n, f) => n + f.approxSizeBytes, 0),
249
+ async *run(signal) {
250
+ const provider = await imageProvider.current();
251
+ yield* provider.pullModel(id, spec, signal);
252
+ },
253
+ };
254
+ };
255
+ }
256
+ /**
257
+ * LoRA pulls: the catalog's `image-lora` entries, installed by whichever
258
+ * provider owns the engine. A provider without LoRA support fails the pull
259
+ * with a plain message.
260
+ */
261
+ export function imageLoraPullResolver(imageProvider, catalog) {
262
+ return async (id) => {
263
+ const item = await catalog.get('image-lora', id);
264
+ if (!item || item.manifest.kind !== 'image-lora')
265
+ return null;
266
+ const manifest = item.manifest;
267
+ const spec = {
268
+ downloadUrl: manifest.downloadUrl,
269
+ sha256: manifest.sha256,
270
+ approxSizeBytes: manifest.approxSizeBytes,
271
+ name: manifest.name,
272
+ baseModels: manifest.baseModels,
273
+ purpose: manifest.purpose,
274
+ strength: manifest.strength,
275
+ ...(manifest.prompt ? { prompt: manifest.prompt } : {}),
276
+ ...(manifest.render ? { render: manifest.render } : {}),
277
+ };
278
+ return {
279
+ totalBytes: manifest.approxSizeBytes,
280
+ async *run(signal) {
281
+ const provider = await imageProvider.current();
282
+ if (!provider.pullLora) {
283
+ throw new Error('This image engine cannot add styles or fast mode.');
284
+ }
285
+ yield* provider.pullLora(id, spec, signal);
286
+ },
287
+ };
288
+ };
289
+ }
@@ -15,10 +15,11 @@
15
15
  * catalog manifest (so local state is self-describing even if the
16
16
  * catalog entry later disappears).
17
17
  */
18
+ import { type ImageLoraPrompt, type ImageLoraPurpose, type ImageLoraRender } from '@bendyline/gezel';
18
19
  import { type ModelStorageRoots } from '../../models/storage-roots.js';
19
20
  import type { GpuArbiter } from '../gpu-arbiter.js';
20
21
  import type { NativeEngineSupervisor } from '../native/supervisor.js';
21
- import { type ImageEngineHealth, type ImageGenerationInput, type ImageGenerationOutput, type ImageGenerationProgress, type ImageModelPullEvent, type ImageModelPullSpec, type ImageProvider, type InstalledImageModelInfo } from './types.js';
22
+ import { type ImageEngineHealth, type ImageGenerationInput, type ImageGenerationOutput, type ImageGenerationProgress, type ImageLoraPullSpec, type ImageModelPullEvent, type ImageModelPullSpec, type ImageProvider, type InstalledImageLoraInfo, type InstalledImageModelInfo } from './types.js';
22
23
  export interface StableDiffusionCppProviderOptions {
23
24
  /** Base URL of the running sd-server (no trailing slash). */
24
25
  baseUrl: string;
@@ -26,6 +27,13 @@ export interface StableDiffusionCppProviderOptions {
26
27
  modelsRoot: string;
27
28
  /** Optional read-only machine-store overlay. */
28
29
  storageRoots?: ModelStorageRoots;
30
+ /**
31
+ * Where LoRAs install (`modelStorageRoots({ engine: 'sd-cpp-loras' })`).
32
+ * Only the writable root reaches the engine, which takes one
33
+ * `--lora-model-dir`. Absent when the engine is someone else's server
34
+ * (`GEZEL_SD_SERVER_URL`): renders then go without LoRAs, with a note.
35
+ */
36
+ loraRoots?: ModelStorageRoots;
29
37
  /** Awake-time budget for one render request, in ms. Defaults to 30 minutes. */
30
38
  timeoutMs?: number;
31
39
  /** Injected for tests. Defaults to global fetch. */
@@ -130,6 +138,8 @@ export interface SdServerLoadProfile {
130
138
  * measured not to change.
131
139
  */
132
140
  stepCache?: 'easycache';
141
+ /** `--model-args` key=value list. See MODEL_ARGS. */
142
+ modelArgs?: string;
133
143
  }
134
144
  export declare function sdServerLoadProfile(modelId: string): SdServerLoadProfile;
135
145
  export declare class StableDiffusionCppProvider implements ImageProvider {
@@ -137,6 +147,7 @@ export declare class StableDiffusionCppProvider implements ImageProvider {
137
147
  private readonly baseUrl;
138
148
  private readonly modelsRoot;
139
149
  private readonly storageRoots;
150
+ private readonly loraRoots?;
140
151
  private readonly timeoutMs;
141
152
  private readonly fetchImpl;
142
153
  private readonly supervisor?;
@@ -150,6 +161,28 @@ export declare class StableDiffusionCppProvider implements ImageProvider {
150
161
  generate(input: ImageGenerationInput): Promise<ImageGenerationOutput>;
151
162
  private generateInner;
152
163
  listInstalledModels(): Promise<InstalledImageModelInfo[]>;
164
+ /** Installed LoRAs, from the root the engine reads; none when this engine takes none. */
165
+ listLoras(): Promise<InstalledImageLoraInfo[]>;
166
+ /**
167
+ * Install one LoRA: a single safetensors file, sha256-checked like a model,
168
+ * beside a `manifest.json` that keeps the catalog's recipe so the engine can
169
+ * apply it without the catalog.
170
+ */
171
+ pullLora(id: string, spec: ImageLoraPullSpec, signal?: AbortSignal): AsyncIterable<ImageModelPullEvent>;
172
+ deleteLora(id: string): Promise<void>;
173
+ /**
174
+ * The LoRAs a render asked for that this engine can apply to `modelId`:
175
+ * installed in the root the engine reads and checked on that model. The
176
+ * rest are dropped with a note, never an error.
177
+ */
178
+ private resolveLoras;
179
+ /**
180
+ * Keep the LoRAs the running engine can see. sd-server fills its LoRA list
181
+ * only when it is asked for it, and answers `invalid lora path` for any LoRA
182
+ * named before then, so the list is read before every LoRA render.
183
+ */
184
+ private lorasTheEngineHas;
185
+ private readInstalledLora;
153
186
  pullModel(id: string, spec: ImageModelPullSpec, signal?: AbortSignal): AsyncIterable<ImageModelPullEvent>;
154
187
  /**
155
188
  * Stream a single URL to disk with sha256 verification, reporting cumulative
@@ -167,11 +200,13 @@ export declare class StableDiffusionCppProvider implements ImageProvider {
167
200
  health(): Promise<ImageEngineHealth>;
168
201
  shutdown(): Promise<void>;
169
202
  /**
170
- * The configured default when it is installed, else the first installed
171
- * model id, else undefined. Stable ordering matches
172
- * {@link listInstalledModels} so the default is deterministic.
203
+ * The model a fresh picture renders with: the configured default when it
204
+ * is installed and draws, else the first installed model that draws. Stable
205
+ * ordering matches {@link listInstalledModels} so the default is
206
+ * deterministic.
173
207
  */
174
208
  resolveDefaultModelId(): Promise<string | undefined>;
209
+ private chooseModel;
175
210
  private defaultSampleSteps;
176
211
  /**
177
212
  * Read the installed `manifest.json` fields that steer per-model
@@ -182,6 +217,30 @@ export declare class StableDiffusionCppProvider implements ImageProvider {
182
217
  */
183
218
  private readInstalledManifestFields;
184
219
  }
220
+ /**
221
+ * sd-server reads `<sd_cpp_extra_args>{json}</sd_cpp_extra_args>` out of a
222
+ * prompt and applies the JSON as generation parameters, including LoRA and
223
+ * ADetailer model paths it never checks. Prompts come from gezels reading
224
+ * untrusted content and from other apps, so the tag never reaches the engine.
225
+ * Repeated until stable: removing one tag can join the pieces around it into
226
+ * another.
227
+ */
228
+ export declare function withoutEngineDirectives(text: string): string;
229
+ /** An installed LoRA with the recipe its catalog entry gave it. */
230
+ export interface InstalledLora {
231
+ id: string;
232
+ name: string;
233
+ filename: string;
234
+ purpose: ImageLoraPurpose;
235
+ strength: number;
236
+ baseModels: string[];
237
+ prompt?: ImageLoraPrompt;
238
+ render?: ImageLoraRender;
239
+ approxSizeBytes: number;
240
+ installedAt: string;
241
+ }
242
+ /** Every LoRA installed under `loraRoot`, skipping malformed ones. */
243
+ export declare function readInstalledLoras(loraRoot: string): Promise<InstalledLora[]>;
185
244
  /**
186
245
  * Parse one line of sd-server's weight-load bar, which it prints inside the
187
246
  * first request after a launch because it loads weights lazily: