bleam 0.0.9 → 0.0.11

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 (68) hide show
  1. package/dist/ai.cjs +608 -59
  2. package/dist/ai.d.cts +217 -89
  3. package/dist/ai.d.ts +217 -89
  4. package/dist/ai.js +601 -59
  5. package/dist/cli.cjs +142 -94
  6. package/dist/cli.d.cts +11 -27
  7. package/dist/cli.d.ts +11 -27
  8. package/dist/cli.js +143 -95
  9. package/dist/files.d.cts +1 -1
  10. package/dist/files.d.ts +1 -1
  11. package/dist/platform.cjs +1 -13
  12. package/dist/platform.d.cts +1 -26
  13. package/dist/platform.d.ts +1 -26
  14. package/dist/platform.js +1 -10
  15. package/dist/schema.d.cts +1 -1
  16. package/dist/schema.d.ts +1 -1
  17. package/dist/state.d.cts +1 -1
  18. package/dist/state.d.ts +1 -1
  19. package/dist/{ui-Bg11tvlc.d.ts → ui-CHc4xEs_.d.ts} +7 -7
  20. package/dist/ui.d.ts +1 -1
  21. package/dist/window.d.ts +1 -1
  22. package/package.json +2 -1
  23. package/templates/image-generation/app/index.tsx +3 -1
  24. package/templates/native/ios/Bleam/AI/Flux2/Configuration/ModelRegistry.swift +1 -1
  25. package/templates/native/ios/Bleam/AI/Flux2/Pipeline/FluxImageRunner.swift +7 -2
  26. package/templates/native/ios/Bleam/AI/TextGeneration/Bonsai/BonsaiTextGenerator.swift +46 -0
  27. package/templates/native/ios/Bleam/AI/TextGeneration/Bonsai/BonsaiTextModel.swift +107 -0
  28. package/templates/native/ios/Bleam/AI/TextGeneration/Bonsai/BonsaiTextSnapshotValidator.swift +201 -0
  29. package/templates/native/ios/Bleam/AI/TextGeneration/Core/GenerationTypes.swift +85 -0
  30. package/templates/native/ios/Bleam/AI/TextGeneration/Core/ModelCapabilities.swift +21 -0
  31. package/templates/native/ios/Bleam/AI/TextGeneration/Core/TextKVCache.swift +23 -0
  32. package/templates/native/ios/Bleam/AI/TextGeneration/Core/TokenSampler.swift +79 -0
  33. package/templates/native/ios/Bleam/AI/TextGeneration/Qwen3/Qwen3CheckpointLoader.swift +161 -0
  34. package/templates/native/ios/Bleam/AI/TextGeneration/Qwen3/Qwen3Configuration.swift +119 -0
  35. package/templates/native/ios/Bleam/AI/TextGeneration/Qwen3/Qwen3InferenceSession.swift +84 -0
  36. package/templates/native/ios/Bleam/AI/TextGeneration/Qwen3/Qwen3Layers.swift +197 -0
  37. package/templates/native/ios/Bleam/AI/TextGeneration/Qwen3/Qwen3Model.swift +130 -0
  38. package/templates/native/ios/Bleam/AI/TextGeneration/Qwen35/Qwen35Checkpoint.swift +102 -0
  39. package/templates/native/ios/Bleam/AI/TextGeneration/Qwen35/Qwen35Configuration.swift +140 -0
  40. package/templates/native/ios/Bleam/AI/TextGeneration/Qwen35/Qwen35Model.swift +112 -0
  41. package/templates/native/ios/Bleam/AI/TextGeneration/Qwen35/Qwen35Session.swift +121 -0
  42. package/templates/native/ios/Bleam/AI/TextGeneration/QwenHybrid/QwenHybridCache.swift +42 -0
  43. package/templates/native/ios/Bleam/AI/TextGeneration/QwenHybrid/QwenHybridFullAttention.swift +57 -0
  44. package/templates/native/ios/Bleam/AI/TextGeneration/QwenHybrid/QwenHybridLayers.swift +58 -0
  45. package/templates/native/ios/Bleam/AI/TextGeneration/QwenHybrid/QwenHybridLinearAttention.swift +111 -0
  46. package/templates/native/ios/Bleam/AppDelegate.swift +0 -97
  47. package/templates/native/ios/Bleam.xcodeproj/project.pbxproj +128 -121
  48. package/templates/native/ios/GenerationService/BonsaiTextGenerationRunner.swift +103 -0
  49. package/templates/native/ios/GenerationService/ImageGenerationRunner.swift +19 -7
  50. package/templates/native/ios/GenerationService/main.swift +174 -48
  51. package/templates/native/ios/Shared/Generation/GenerationServiceProtocol.swift +3 -0
  52. package/templates/native/ios/Shared/Generation/GenerationWorkerProtocol.swift +44 -0
  53. package/templates/native/modules/bleam-runtime/ios/AIModule.swift +225 -5
  54. package/templates/native/modules/bleam-runtime/ios/GenerationContracts.swift +47 -0
  55. package/templates/native/modules/bleam-runtime/ios/PlatformModule.swift +0 -175
  56. package/templates/text-generation/app/index.tsx +93 -0
  57. package/templates/text-generation/app.config.ts +5 -0
  58. package/templates/updates/README.md +116 -0
  59. package/templates/updates/src/index.ts +292 -0
  60. package/templates/updates/src/schema.ts +230 -0
  61. package/templates/updates/tsconfig.json +12 -0
  62. package/templates/updates/wrangler.jsonc +19 -0
  63. package/templates/native/ios/PlatformHelper/Info.plist +0 -29
  64. package/templates/native/ios/PlatformHelper/main.swift +0 -335
  65. /package/dist/{files-DwA7pzr3.d.cts → files-4ZEoAWiv.d.ts} +0 -0
  66. /package/dist/{files-VrkQlKIT.d.ts → files-Dt5mbzLq.d.cts} +0 -0
  67. /package/dist/{schema-DOOjfXvs.d.ts → schema-CYh6n8GS.d.ts} +0 -0
  68. /package/dist/{schema-ENSMX_1t.d.cts → schema-oeOrd3i1.d.cts} +0 -0
package/dist/ai.js CHANGED
@@ -7,6 +7,53 @@ import { requireNativeModule } from "expo";
7
7
  import { useRef, useState } from "react";
8
8
  import { Image } from "react-native";
9
9
 
10
+ //#region src/ai/bonsai.ts
11
+ const internalTextModelCatalog = [
12
+ {
13
+ id: "ternary-bonsai-1.7b",
14
+ repo: "prism-ml/Ternary-Bonsai-1.7B-mlx-2bit",
15
+ revision: "5f3e306330f636cfc6c6241b4850fae6711c5985",
16
+ estimatedDownloadBytes: 495528947,
17
+ contextLength: 32768,
18
+ architecture: "qwen3"
19
+ },
20
+ {
21
+ id: "ternary-bonsai-4b",
22
+ repo: "prism-ml/Ternary-Bonsai-4B-mlx-2bit",
23
+ revision: "e1374ad6bf9b1b56afd743936b8faa33c409a75f",
24
+ estimatedDownloadBytes: 1143060044,
25
+ contextLength: 32768,
26
+ architecture: "qwen3"
27
+ },
28
+ {
29
+ id: "ternary-bonsai-8b",
30
+ repo: "prism-ml/Ternary-Bonsai-8B-mlx-2bit",
31
+ revision: "9260b24298e4211e804663e9f519962cf59f34be",
32
+ estimatedDownloadBytes: 2315155948,
33
+ contextLength: 65536,
34
+ architecture: "qwen3"
35
+ },
36
+ {
37
+ id: "ternary-bonsai-27b",
38
+ repo: "prism-ml/Ternary-Bonsai-27B-mlx-2bit",
39
+ revision: "70f75f3ad081ab840a42f3304c02c27e7f89bfb7",
40
+ estimatedDownloadBytes: 8510972679,
41
+ contextLength: 262144,
42
+ architecture: "qwen3.5"
43
+ }
44
+ ];
45
+ const bonsaiTextModelCatalog = Object.freeze(internalTextModelCatalog.map(({ architecture: _,...metadata }) => Object.freeze(metadata)));
46
+ function bonsai(id) {
47
+ const text = internalTextModelCatalog.some((model) => model.id === id);
48
+ if (!text && id !== "ternary-bonsai-image-4b") throw new TypeError(`Unsupported Bonsai model: ${id}`);
49
+ return Object.freeze({
50
+ provider: "bonsai",
51
+ kind: text ? "text" : "image",
52
+ id
53
+ });
54
+ }
55
+
56
+ //#endregion
10
57
  //#region src/ai/errors.ts
11
58
  var AIError = class extends Error {
12
59
  constructor(code, message, cause) {
@@ -46,6 +93,217 @@ function mapNativeError(error) {
46
93
  return new AIError("generation_failed", message, error);
47
94
  }
48
95
 
96
+ //#endregion
97
+ //#region src/ai/bonsai-text.ts
98
+ const bonsaiTextModelFiles = [
99
+ "config.json",
100
+ "tokenizer.json",
101
+ "tokenizer_config.json",
102
+ "chat_template.jinja",
103
+ "model.safetensors",
104
+ "model.safetensors.index.json"
105
+ ];
106
+ function modelId(model) {
107
+ return typeof model === "string" ? model : model.id;
108
+ }
109
+ function metadataFor(model) {
110
+ const id = modelId(model);
111
+ const metadata = bonsaiTextModelCatalog.find((candidate) => candidate.id === id);
112
+ if (!metadata) throw new AIError("invalid_model", `Unsupported Bonsai text model: ${id}`);
113
+ return metadata;
114
+ }
115
+ function bonsaiTextModelDirectory(model) {
116
+ return files.directory(files.paths.models, "bonsai", "text", modelId(model));
117
+ }
118
+ function modelDirectory(model, override) {
119
+ return override ? files.directory(override) : bonsaiTextModelDirectory(model);
120
+ }
121
+ function modelDirectoryIn(model, directory) {
122
+ return directory ? files.directory(directory, modelId(model)) : bonsaiTextModelDirectory(model);
123
+ }
124
+ function resolveUrl(repo, revision, file) {
125
+ return `https://huggingface.co/${repo.split("/").map(encodeURIComponent).join("/")}/resolve/${encodeURIComponent(revision)}/${encodeURIComponent(file)}`;
126
+ }
127
+ function throwIfAborted$1(signal) {
128
+ if (signal?.aborted) throw new AIError("generation_canceled", "Bonsai model preparation canceled");
129
+ }
130
+ const fileSizes = {
131
+ "ternary-bonsai-1.7b": {
132
+ "config.json": 2939,
133
+ "tokenizer.json": 11422650,
134
+ "tokenizer_config.json": 348,
135
+ "chat_template.jinja": 4063,
136
+ "model.safetensors": 484049216,
137
+ "model.safetensors.index.json": 49731
138
+ },
139
+ "ternary-bonsai-4b": {
140
+ "config.json": 3115,
141
+ "tokenizer.json": 11422650,
142
+ "tokenizer_config.json": 348,
143
+ "chat_template.jinja": 4063,
144
+ "model.safetensors": 1131565944,
145
+ "model.safetensors.index.json": 63924
146
+ },
147
+ "ternary-bonsai-8b": {
148
+ "config.json": 3118,
149
+ "tokenizer.json": 11422650,
150
+ "tokenizer_config.json": 348,
151
+ "chat_template.jinja": 4063,
152
+ "model.safetensors": 2303661704,
153
+ "model.safetensors.index.json": 64065
154
+ },
155
+ "ternary-bonsai-27b": {
156
+ "config.json": 3790,
157
+ "tokenizer.json": 19989343,
158
+ "tokenizer_config.json": 1098,
159
+ "chat_template.jinja": 7764,
160
+ "model.safetensors": 8490785104,
161
+ "model.safetensors.index.json": 185580
162
+ }
163
+ };
164
+ function validFile(model, file, size) {
165
+ return size === fileSizes[model][file];
166
+ }
167
+ async function stateFor(model, rootOverride) {
168
+ const metadata = metadataFor(model);
169
+ const root = modelDirectory(model, rootOverride);
170
+ const missingFiles = [];
171
+ const invalidFiles = [];
172
+ let bytesPresent = 0;
173
+ for (const file of bonsaiTextModelFiles) {
174
+ const stat = await root.file(file).stat();
175
+ if (stat.type !== "file") missingFiles.push(file);
176
+ else {
177
+ bytesPresent += stat.size ?? 0;
178
+ if (!validFile(metadata.id, file, stat.size)) invalidFiles.push(file);
179
+ }
180
+ }
181
+ const expectedRevision = metadata.revision;
182
+ const manifestFile = root.file("manifest.json");
183
+ if ((await manifestFile.stat()).type !== "file") missingFiles.push("manifest.json");
184
+ else try {
185
+ const manifest = await manifestFile.json();
186
+ if (!isRecord(manifest) || manifest.formatVersion !== 1 || manifest.model !== metadata.id || manifest.revision !== expectedRevision) invalidFiles.push("manifest.json");
187
+ } catch {
188
+ invalidFiles.push("manifest.json");
189
+ }
190
+ if (!missingFiles.includes("config.json")) try {
191
+ const config = await root.file("config.json").json();
192
+ if (!validConfig(metadata.id, config)) invalidFiles.push("config.json");
193
+ } catch {
194
+ invalidFiles.push("config.json");
195
+ }
196
+ const installed = missingFiles.length === 0;
197
+ return {
198
+ model: bonsai(metadata.id),
199
+ metadata,
200
+ path: root.path,
201
+ installed,
202
+ verified: installed && invalidFiles.length === 0,
203
+ bytesPresent,
204
+ estimatedTotalBytes: metadata.estimatedDownloadBytes,
205
+ missingFiles,
206
+ invalidFiles
207
+ };
208
+ }
209
+ function validConfig(model, value) {
210
+ if (!isRecord(value)) return false;
211
+ const expected = {
212
+ "ternary-bonsai-1.7b": [
213
+ 2048,
214
+ 28,
215
+ 32768
216
+ ],
217
+ "ternary-bonsai-4b": [
218
+ 2560,
219
+ 36,
220
+ 32768
221
+ ],
222
+ "ternary-bonsai-8b": [
223
+ 4096,
224
+ 36,
225
+ 65536
226
+ ],
227
+ "ternary-bonsai-27b": [
228
+ 5120,
229
+ 64,
230
+ 262144
231
+ ]
232
+ }[model];
233
+ const config = model === "ternary-bonsai-27b" ? value.text_config : value;
234
+ if (!isRecord(config)) return false;
235
+ const quantization = config.quantization;
236
+ return isRecord(quantization) && quantization.bits === 2 && quantization.group_size === 128 && config.hidden_size === expected[0] && config.num_hidden_layers === expected[1] && config.max_position_embeddings === expected[2];
237
+ }
238
+ async function prepareBonsaiTextModel(model, options = {}) {
239
+ const metadata = metadataFor(model);
240
+ const root = modelDirectoryIn(model, options.directory);
241
+ throwIfAborted$1(options.signal);
242
+ await root.create({ recursive: true });
243
+ const headers = options.accessToken ? { Authorization: `Bearer ${options.accessToken}` } : void 0;
244
+ const completedBytes = /* @__PURE__ */ new Map();
245
+ const emit = (file, index, progress) => {
246
+ completedBytes.set(file, progress.bytesWritten);
247
+ const modelBytesWritten = Array.from(completedBytes.values()).reduce((total, bytes) => total + bytes, 0);
248
+ options.onProgress?.({
249
+ model: metadata.id,
250
+ file,
251
+ index,
252
+ total: bonsaiTextModelFiles.length,
253
+ ...progress,
254
+ modelBytesWritten,
255
+ estimatedModelBytes: metadata.estimatedDownloadBytes,
256
+ modelFraction: Math.min(1, modelBytesWritten / metadata.estimatedDownloadBytes)
257
+ });
258
+ };
259
+ for (const [fileIndex, file] of bonsaiTextModelFiles.entries()) {
260
+ throwIfAborted$1(options.signal);
261
+ const index = fileIndex + 1;
262
+ const destination = root.file(file);
263
+ const expectedBytes = fileSizes[metadata.id][file];
264
+ const stat = await destination.stat();
265
+ if (stat.type === "file" && validFile(metadata.id, file, stat.size)) {
266
+ emit(file, index, {
267
+ bytesWritten: stat.size ?? 0,
268
+ totalBytes: stat.size,
269
+ fraction: 1
270
+ });
271
+ continue;
272
+ }
273
+ await destination.download(resolveUrl(metadata.repo, metadata.revision, file), {
274
+ headers,
275
+ expectedBytes,
276
+ atomic: true,
277
+ resume: true,
278
+ signal: options.signal,
279
+ onProgress(progress) {
280
+ emit(file, index, progress);
281
+ }
282
+ }).result;
283
+ throwIfAborted$1(options.signal);
284
+ }
285
+ await root.file("manifest.json").write(JSON.stringify({
286
+ formatVersion: 1,
287
+ model: metadata.id,
288
+ revision: metadata.revision
289
+ }));
290
+ const state = await stateFor(metadata.id, root.path);
291
+ if (!state.verified) throw new AIError("invalid_model", `Downloaded Bonsai model is incomplete: ${[...state.missingFiles, ...state.invalidFiles].join(", ")}`);
292
+ return {
293
+ model: state.model,
294
+ path: root.path
295
+ };
296
+ }
297
+ function verifyBonsaiTextModel(model, options = {}) {
298
+ return stateFor(model, options.path);
299
+ }
300
+ function listBonsaiTextModels(options = {}) {
301
+ return Promise.all(bonsaiTextModelCatalog.map((metadata) => stateFor(metadata.id, options.directory ? files.directory(options.directory, metadata.id).path : void 0)));
302
+ }
303
+ async function deleteBonsaiTextModel(model, options = {}) {
304
+ await modelDirectory(model, options.path).delete({ recursive: true });
305
+ }
306
+
49
307
  //#endregion
50
308
  //#region src/ai/image-batch.ts
51
309
  function batchImageSeeds(count, seed) {
@@ -243,7 +501,51 @@ const flux2Klein4B8BitAbliterated = {
243
501
  }
244
502
  }
245
503
  };
246
- const imageModelManifests = [flux2Klein4B8BitAbliterated];
504
+ const ternaryBonsaiImage4B = {
505
+ formatVersion: 1,
506
+ model: "ternary-bonsai-image-4b",
507
+ directory: "ternary-bonsai-image-4b",
508
+ metadata: {
509
+ displayName: "Ternary Bonsai Image 4B",
510
+ family: "bonsai-image",
511
+ precision: "2-bit",
512
+ estimatedMemoryGB: 8,
513
+ quality: "high",
514
+ speed: "fast",
515
+ capabilities: ["text-to-image"]
516
+ },
517
+ components: {
518
+ textEncoder: {
519
+ repo: "prism-ml/bonsai-image-ternary-4B-mlx-2bit",
520
+ revision: "2c24c81b934a658ba5590cf39088ba929985b4a8",
521
+ path: "text-encoder",
522
+ files: [
523
+ "text_encoder-mlx-4bit/config.json",
524
+ "text_encoder-mlx-4bit/tokenizer.json",
525
+ "text_encoder-mlx-4bit/tokenizer_config.json",
526
+ "text_encoder-mlx-4bit/model.safetensors",
527
+ "text_encoder-mlx-4bit/model.safetensors.index.json"
528
+ ]
529
+ },
530
+ transformer: {
531
+ repo: "prism-ml/bonsai-image-ternary-4B-mlx-2bit",
532
+ revision: "2c24c81b934a658ba5590cf39088ba929985b4a8",
533
+ path: "transformer",
534
+ files: [
535
+ "transformer-packed-mflux/config.json",
536
+ "transformer-packed-mflux/diffusion_pytorch_model.safetensors",
537
+ "transformer-packed-mflux/quantization_config.json"
538
+ ]
539
+ },
540
+ vae: {
541
+ repo: "prism-ml/bonsai-image-ternary-4B-mlx-2bit",
542
+ revision: "2c24c81b934a658ba5590cf39088ba929985b4a8",
543
+ path: "vae",
544
+ files: ["vae/config.json", "vae/diffusion_pytorch_model.safetensors"]
545
+ }
546
+ }
547
+ };
548
+ const imageModelManifests = [flux2Klein4B8BitAbliterated, ternaryBonsaiImage4B];
247
549
  function imageModelManifest(model) {
248
550
  return imageModelManifests.find((manifest) => manifest.model === model);
249
551
  }
@@ -313,15 +615,90 @@ const flux2KleinModelFiles = [
313
615
  }
314
616
  ];
315
617
  const flux2KleinTotalBytes = flux2KleinModelFiles.reduce((total, file) => total + file.bytes, 0);
618
+ const bonsaiRepo = ternaryBonsaiImage4B.components.textEncoder.repo;
619
+ const bonsaiRevision = ternaryBonsaiImage4B.components.textEncoder.revision;
620
+ const ternaryBonsaiImageModelFiles = [
621
+ {
622
+ repo: bonsaiRepo,
623
+ revision: bonsaiRevision,
624
+ source: "text_encoder-mlx-4bit/config.json",
625
+ destination: "text-encoder/config.json",
626
+ bytes: 937
627
+ },
628
+ {
629
+ repo: bonsaiRepo,
630
+ revision: bonsaiRevision,
631
+ source: "text_encoder-mlx-4bit/tokenizer.json",
632
+ destination: "text-encoder/tokenizer.json",
633
+ bytes: 11422654
634
+ },
635
+ {
636
+ repo: bonsaiRepo,
637
+ revision: bonsaiRevision,
638
+ source: "text_encoder-mlx-4bit/tokenizer_config.json",
639
+ destination: "text-encoder/tokenizer_config.json",
640
+ bytes: 9706
641
+ },
642
+ {
643
+ repo: bonsaiRepo,
644
+ revision: bonsaiRevision,
645
+ source: "text_encoder-mlx-4bit/model.safetensors",
646
+ destination: "text-encoder/model.safetensors",
647
+ bytes: 2263022529
648
+ },
649
+ {
650
+ repo: bonsaiRepo,
651
+ revision: bonsaiRevision,
652
+ source: "text_encoder-mlx-4bit/model.safetensors.index.json",
653
+ destination: "text-encoder/model.safetensors.index.json",
654
+ bytes: 63924
655
+ },
656
+ {
657
+ repo: bonsaiRepo,
658
+ revision: bonsaiRevision,
659
+ source: "transformer-packed-mflux/config.json",
660
+ destination: "transformer/config.json",
661
+ bytes: 619
662
+ },
663
+ {
664
+ repo: bonsaiRepo,
665
+ revision: bonsaiRevision,
666
+ source: "transformer-packed-mflux/diffusion_pytorch_model.safetensors",
667
+ destination: "transformer/diffusion_pytorch_model.safetensors",
668
+ bytes: 1425271472
669
+ },
670
+ {
671
+ repo: bonsaiRepo,
672
+ revision: bonsaiRevision,
673
+ source: "transformer-packed-mflux/quantization_config.json",
674
+ destination: "transformer/quantization_config.json",
675
+ bytes: 5054
676
+ },
677
+ {
678
+ repo: bonsaiRepo,
679
+ revision: bonsaiRevision,
680
+ source: "vae/config.json",
681
+ destination: "vae/config.json",
682
+ bytes: 821
683
+ },
684
+ {
685
+ repo: bonsaiRepo,
686
+ revision: bonsaiRevision,
687
+ source: "vae/diffusion_pytorch_model.safetensors",
688
+ destination: "vae/diffusion_pytorch_model.safetensors",
689
+ bytes: 168120878
690
+ }
691
+ ];
692
+ const ternaryBonsaiImageTotalBytes = ternaryBonsaiImageModelFiles.reduce((total, file) => total + file.bytes, 0);
316
693
  function joinModelPath(root, component) {
317
694
  return `${root.replace(/\/+$/, "")}/${component.replace(/^\/+/, "")}`;
318
695
  }
319
- function fluxImageModelPaths(modelRoot, manifest = flux2Klein4B8BitAbliterated) {
696
+ function fluxImageModelPaths(path, manifest = flux2Klein4B8BitAbliterated) {
320
697
  return {
321
- modelRoot,
322
- textEncoderPath: joinModelPath(modelRoot, manifest.components.textEncoder.path),
323
- transformerPath: joinModelPath(modelRoot, manifest.components.transformer.path),
324
- vaePath: joinModelPath(modelRoot, manifest.components.vae.path)
698
+ path,
699
+ textEncoderPath: joinModelPath(path, manifest.components.textEncoder.path),
700
+ transformerPath: joinModelPath(path, manifest.components.transformer.path),
701
+ vaePath: joinModelPath(path, manifest.components.vae.path)
325
702
  };
326
703
  }
327
704
 
@@ -2420,14 +2797,31 @@ var Tokenizer_default = Tokenizer;
2420
2797
 
2421
2798
  //#endregion
2422
2799
  //#region src/ai/image.ts
2423
- function modelCacheRoot(manifest = flux2Klein4B8BitAbliterated) {
2800
+ function imageModelId(model) {
2801
+ return typeof model === "string" ? model : model.id;
2802
+ }
2803
+ function imageModelDefinition(model) {
2804
+ const id = imageModelId(model);
2805
+ if (id === flux2Klein4B8BitAbliterated.model) return {
2806
+ manifest: flux2Klein4B8BitAbliterated,
2807
+ files: flux2KleinModelFiles,
2808
+ totalBytes: flux2KleinTotalBytes
2809
+ };
2810
+ if (id === ternaryBonsaiImage4B.model) return {
2811
+ manifest: ternaryBonsaiImage4B,
2812
+ files: ternaryBonsaiImageModelFiles,
2813
+ totalBytes: ternaryBonsaiImageTotalBytes
2814
+ };
2815
+ throw new AIError("invalid_model", `Unsupported image model: ${id}`);
2816
+ }
2817
+ function modelCacheRoot(manifest) {
2424
2818
  return files.directory(files.paths.models, manifest.directory);
2425
2819
  }
2426
2820
  function assertImageModel(model) {
2427
- if (model !== flux2Klein4B8BitAbliterated.model) throw new AIError("invalid_model", `Unsupported image model: ${model}`);
2821
+ imageModelDefinition(model);
2428
2822
  }
2429
- function modelRootFromOptions(modelRoot) {
2430
- return modelRoot ?? modelCacheRoot().path;
2823
+ function modelPathFromOptions(definition, path) {
2824
+ return path ?? modelCacheRoot(definition.manifest).path;
2431
2825
  }
2432
2826
  function downloadConcurrency(value) {
2433
2827
  if (value === void 0) return 2;
@@ -2594,11 +2988,11 @@ function cachedLoRAFile(lora) {
2594
2988
  return imageLoRARoot().directory(id).file(safeCacheName(sourceFileName(lora), "lora filename"));
2595
2989
  }
2596
2990
  async function ensureModelFile(input) {
2597
- const destination = files.file(input.modelRoot, input.file.destination);
2991
+ const destination = files.file(input.path, input.file.destination);
2598
2992
  const stat = await destination.stat();
2599
2993
  if (stat.type === "file" && stat.size === input.file.bytes) {
2600
2994
  input.onProgress?.({
2601
- model: flux2Klein4B8BitAbliterated.model,
2995
+ model: input.model,
2602
2996
  file: input.file.destination,
2603
2997
  index: input.index,
2604
2998
  total: input.total,
@@ -2615,7 +3009,7 @@ async function ensureModelFile(input) {
2615
3009
  resume: true,
2616
3010
  onProgress(progress) {
2617
3011
  input.onProgress?.({
2618
- model: flux2Klein4B8BitAbliterated.model,
3012
+ model: input.model,
2619
3013
  file: input.file.destination,
2620
3014
  index: input.index,
2621
3015
  total: input.total,
@@ -2624,16 +3018,16 @@ async function ensureModelFile(input) {
2624
3018
  }
2625
3019
  }).result;
2626
3020
  }
2627
- async function writePreparedManifest(modelRoot) {
2628
- await files.file(modelRoot, "manifest.json").write(JSON.stringify(flux2Klein4B8BitAbliterated, null, 2));
3021
+ async function writePreparedManifest(path, manifest) {
3022
+ await files.file(path, "manifest.json").write(JSON.stringify(manifest, null, 2));
2629
3023
  }
2630
- async function imageModelState(model, modelRoot) {
2631
- assertImageModel(model);
3024
+ async function imageModelState(model, path) {
3025
+ const definition = imageModelDefinition(model);
2632
3026
  let bytesPresent = 0;
2633
3027
  const missingFiles = [];
2634
3028
  const invalidFiles = [];
2635
- for (const file of flux2KleinModelFiles) {
2636
- const stat = await files.file(modelRoot, file.destination).stat();
3029
+ for (const file of definition.files) {
3030
+ const stat = await files.file(path, file.destination).stat();
2637
3031
  if (stat.type !== "file") {
2638
3032
  missingFiles.push(file.destination);
2639
3033
  continue;
@@ -2641,17 +3035,17 @@ async function imageModelState(model, modelRoot) {
2641
3035
  bytesPresent += Math.min(stat.size ?? 0, file.bytes);
2642
3036
  if (stat.size !== file.bytes) invalidFiles.push(file.destination);
2643
3037
  }
2644
- const installed = missingFiles.length === 0;
2645
- const manifestStat = await files.file(modelRoot, "manifest.json").stat();
3038
+ const manifestStat = await files.file(path, "manifest.json").stat();
2646
3039
  if (manifestStat.type !== "file") missingFiles.push("manifest.json");
3040
+ const installed = missingFiles.length === 0;
2647
3041
  return {
2648
3042
  model,
2649
- modelRoot,
2650
- paths: fluxImageModelPaths(modelRoot),
3043
+ path,
3044
+ paths: fluxImageModelPaths(path, definition.manifest),
2651
3045
  installed,
2652
3046
  verified: installed && invalidFiles.length === 0 && manifestStat.type === "file",
2653
3047
  bytesPresent,
2654
- totalBytes: flux2KleinTotalBytes,
3048
+ totalBytes: definition.totalBytes,
2655
3049
  missingFiles,
2656
3050
  invalidFiles
2657
3051
  };
@@ -2666,8 +3060,8 @@ function tokenArray(value, name) {
2666
3060
  return number;
2667
3061
  });
2668
3062
  }
2669
- async function createFluxTokenizer(modelRoot) {
2670
- const paths = fluxImageModelPaths(modelRoot);
3063
+ async function createFluxTokenizer(path, manifest) {
3064
+ const paths = fluxImageModelPaths(path, manifest);
2671
3065
  const tokenizer = new Tokenizer_default(await files.file(paths.textEncoderPath, "tokenizer.json").json(), await files.file(paths.textEncoderPath, "tokenizer_config.json").json());
2672
3066
  const padTokenId = 151643;
2673
3067
  return ({ prompt, maxLength }) => {
@@ -2714,18 +3108,18 @@ function assertTokenArray(value, name) {
2714
3108
  if (value.length === 0) throw new AIError("invalid_message", `${name} must not be empty`);
2715
3109
  for (const token of value) if (!Number.isInteger(token) || token < 0) throw new AIError("invalid_message", `${name} must contain token ids`);
2716
3110
  }
2717
- function imageResult(value, fallbackMetadata) {
2718
- if (!isRecord(value) || typeof value.uri !== "string" || typeof value.width !== "number" || typeof value.height !== "number" || value.model !== "flux2-klein-4b-8bit-abliterated") throw new AIError("image_generation_failed", "Expected generated image URI");
3111
+ function imageResult(value, model, fallbackMetadata) {
3112
+ if (!isRecord(value) || typeof value.uri !== "string" || typeof value.width !== "number" || typeof value.height !== "number" || value.model !== model) throw new AIError("image_generation_failed", "Expected generated image URI");
2719
3113
  const result = value;
2720
3114
  const metadata = isRecord(result.metadata) ? result.metadata : {};
2721
3115
  return {
2722
3116
  uri: value.uri,
2723
3117
  width: value.width,
2724
3118
  height: value.height,
2725
- model: value.model,
3119
+ model,
2726
3120
  metadata: {
2727
3121
  ...fallbackMetadata,
2728
- model: metadata.model === value.model ? metadata.model : fallbackMetadata.model,
3122
+ model: metadata.model === model ? model : fallbackMetadata.model,
2729
3123
  width: typeof metadata.width === "number" ? metadata.width : value.width,
2730
3124
  height: typeof metadata.height === "number" ? metadata.height : value.height,
2731
3125
  steps: typeof metadata.steps === "number" ? metadata.steps : fallbackMetadata.steps,
@@ -2774,15 +3168,18 @@ function nativeErrorMessage(error) {
2774
3168
  return (error instanceof Error ? error.message : String(error)).replace(/^\[(download|tokenize|encode|denoise|decode|write)]\s*/, "");
2775
3169
  }
2776
3170
  async function prepareImageModel(options = {}) {
2777
- const model = options.model ?? flux2Klein4B8BitAbliterated.model;
2778
- assertImageModel(model);
2779
- const root = modelCacheRoot();
3171
+ const definition = imageModelDefinition(options.model ?? flux2Klein4B8BitAbliterated.model);
3172
+ return prepareImageModelAtPath(definition, options, options.directory ? files.directory(options.directory, definition.manifest.directory).path : modelCacheRoot(definition.manifest).path);
3173
+ }
3174
+ async function prepareImageModelAtPath(definition, options, path) {
3175
+ const model = definition.manifest.model;
3176
+ const root = files.directory(path);
2780
3177
  await root.create({ recursive: true });
2781
3178
  const headers = options.accessToken ? { Authorization: `Bearer ${options.accessToken}` } : void 0;
2782
- const total = flux2KleinModelFiles.length;
3179
+ const total = definition.files.length;
2783
3180
  const bytesByFile = /* @__PURE__ */ new Map();
2784
3181
  const emitProgress = (progress) => {
2785
- const file = flux2KleinModelFiles.find((candidate) => candidate.destination === progress.file);
3182
+ const file = definition.files.find((candidate) => candidate.destination === progress.file);
2786
3183
  const bytesWritten = file ? Math.min(progress.bytesWritten, file.bytes) : progress.bytesWritten;
2787
3184
  bytesByFile.set(progress.file, bytesWritten);
2788
3185
  const modelBytesWritten = Array.from(bytesByFile.values()).reduce((sum, bytes) => sum + bytes, 0);
@@ -2790,12 +3187,12 @@ async function prepareImageModel(options = {}) {
2790
3187
  ...progress,
2791
3188
  bytesWritten,
2792
3189
  modelBytesWritten,
2793
- totalModelBytes: flux2KleinTotalBytes,
2794
- modelFraction: modelBytesWritten / flux2KleinTotalBytes
3190
+ totalModelBytes: definition.totalBytes,
3191
+ modelFraction: modelBytesWritten / definition.totalBytes
2795
3192
  });
2796
3193
  };
2797
3194
  const missingFiles = [];
2798
- for (const [index, file] of flux2KleinModelFiles.entries()) {
3195
+ for (const [index, file] of definition.files.entries()) {
2799
3196
  const stat = await files.file(root.path, file.destination).stat();
2800
3197
  if (stat.type === "file" && stat.size === file.bytes) emitProgress({
2801
3198
  model,
@@ -2813,7 +3210,8 @@ async function prepareImageModel(options = {}) {
2813
3210
  }
2814
3211
  await runConcurrent(missingFiles, downloadConcurrency(options.concurrency), async ({ file, index }) => {
2815
3212
  await ensureModelFile({
2816
- modelRoot: root.path,
3213
+ model,
3214
+ path: root.path,
2817
3215
  file,
2818
3216
  headers,
2819
3217
  index,
@@ -2821,22 +3219,26 @@ async function prepareImageModel(options = {}) {
2821
3219
  onProgress: emitProgress
2822
3220
  });
2823
3221
  });
2824
- await writePreparedManifest(root.path);
3222
+ await writePreparedManifest(root.path, definition.manifest);
2825
3223
  return {
2826
3224
  model,
2827
- ...fluxImageModelPaths(root.path),
2828
- tokenizer: await createFluxTokenizer(root.path)
3225
+ ...fluxImageModelPaths(root.path, definition.manifest),
3226
+ tokenizer: await createFluxTokenizer(root.path, definition.manifest)
2829
3227
  };
2830
3228
  }
2831
3229
  async function verifyImageModel(options = {}) {
2832
- return imageModelState(options.model ?? flux2Klein4B8BitAbliterated.model, modelRootFromOptions(options.modelRoot));
3230
+ const definition = imageModelDefinition(options.model ?? flux2Klein4B8BitAbliterated.model);
3231
+ return imageModelState(definition.manifest.model, modelPathFromOptions(definition, options.path));
2833
3232
  }
2834
3233
  async function listImageModels(options = {}) {
2835
- return [await imageModelState(flux2Klein4B8BitAbliterated.model, modelRootFromOptions(options.modelRoot))];
3234
+ return [...await Promise.all([flux2Klein4B8BitAbliterated.model, ternaryBonsaiImage4B.model].map((model) => {
3235
+ const definition = imageModelDefinition(model);
3236
+ return imageModelState(model, options.directory ? files.directory(options.directory, definition.manifest.directory).path : modelPathFromOptions(definition, void 0));
3237
+ }))];
2836
3238
  }
2837
3239
  async function deleteImageModel(options = {}) {
2838
- assertImageModel(options.model ?? flux2Klein4B8BitAbliterated.model);
2839
- await files.directory(modelRootFromOptions(options.modelRoot)).delete({ recursive: true });
3240
+ const definition = imageModelDefinition(options.model ?? flux2Klein4B8BitAbliterated.model);
3241
+ await files.directory(modelPathFromOptions(definition, options.path)).delete({ recursive: true });
2840
3242
  }
2841
3243
  async function imageMemoryTelemetry() {
2842
3244
  const value = await bridge().imageMemoryTelemetry?.();
@@ -2974,8 +3376,8 @@ async function prepareSelectedImageLoRAs(loras, model, options = {}) {
2974
3376
  }
2975
3377
  async function generateImage(options) {
2976
3378
  throwIfAborted(options.signal);
2977
- const model = options.model ?? flux2Klein4B8BitAbliterated.model;
2978
- assertImageModel(model);
3379
+ const definition = imageModelDefinition(options.model ?? flux2Klein4B8BitAbliterated.model);
3380
+ const model = definition.manifest.model;
2979
3381
  const prompt = options.prompt.trim();
2980
3382
  if (!prompt) throw new AIError("invalid_message", "Expected prompt");
2981
3383
  const presetSize = options.aspectRatio ? imageSizeForPreset(options.aspectRatio) : void 0;
@@ -3034,16 +3436,16 @@ async function generateImage(options) {
3034
3436
  const sigmaSchedule = imageSigmaSchedule(selectedLoRAs.map((lora) => lora.metadata?.sigmaSchedule));
3035
3437
  if (sigmaSchedule && options.steps !== void 0 && options.steps !== sigmaSchedule.length) throw new AIError("invalid_model", `steps must equal the LoRA sigma schedule length (${sigmaSchedule.length})`);
3036
3438
  const generationSteps = sigmaSchedule?.length ?? steps;
3037
- const prepared = options.modelRoot && options.tokenizer ? void 0 : await withImageStage("download", () => prepareImageModel({
3439
+ const prepared = options.path && options.tokenizer ? void 0 : await withImageStage("download", () => prepareImageModelAtPath(definition, {
3038
3440
  model,
3039
3441
  accessToken: options.accessToken,
3040
3442
  concurrency: options.concurrency,
3041
3443
  onProgress: options.onModelProgress
3042
- }));
3444
+ }, options.path ?? modelCacheRoot(definition.manifest).path));
3043
3445
  throwIfAborted(options.signal);
3044
3446
  const tokenizer = options.tokenizer ?? prepared?.tokenizer;
3045
- const modelRoot = options.modelRoot ?? prepared?.modelRoot;
3046
- if (!tokenizer || !modelRoot) throw new AIError("invalid_model", "Expected image model root and tokenizer");
3447
+ const modelPath = options.path ?? prepared?.path;
3448
+ if (!tokenizer || !modelPath) throw new AIError("invalid_model", "Expected image model path and tokenizer");
3047
3449
  const tokenized = await withImageStage("tokenize", () => tokenizer({
3048
3450
  prompt,
3049
3451
  maxLength: 512
@@ -3054,7 +3456,7 @@ async function generateImage(options) {
3054
3456
  assertTokenArray(tokenized.attentionMask, "attentionMask");
3055
3457
  if (tokenized.inputIds.length !== tokenized.attentionMask.length) throw new AIError("invalid_message", "inputIds and attentionMask must have the same length");
3056
3458
  });
3057
- const paths = fluxImageModelPaths(modelRoot);
3459
+ const paths = fluxImageModelPaths(modelPath, definition.manifest);
3058
3460
  const request = {
3059
3461
  model,
3060
3462
  prompt,
@@ -3084,7 +3486,10 @@ async function generateImage(options) {
3084
3486
  seed,
3085
3487
  inputIds: tokenized.inputIds,
3086
3488
  attentionMask: tokenized.attentionMask,
3087
- ...paths
3489
+ modelRoot: paths.path,
3490
+ textEncoderPath: paths.textEncoderPath,
3491
+ transformerPath: paths.transformerPath,
3492
+ vaePath: paths.vaePath
3088
3493
  };
3089
3494
  const fallbackMetadata = {
3090
3495
  model,
@@ -3115,7 +3520,7 @@ async function generateImage(options) {
3115
3520
  throwIfAborted(options.signal);
3116
3521
  const value = await native.generateImage?.(request);
3117
3522
  throwIfAborted(options.signal);
3118
- return imageResult(value, fallbackMetadata);
3523
+ return imageResult(value, model, fallbackMetadata);
3119
3524
  } finally {
3120
3525
  subscription?.remove();
3121
3526
  options.signal?.removeEventListener("abort", cancelNative);
@@ -3883,6 +4288,59 @@ function stableStringify(value) {
3883
4288
  return JSON.stringify(value) ?? "undefined";
3884
4289
  }
3885
4290
 
4291
+ //#endregion
4292
+ //#region src/ai/bonsai-generation.ts
4293
+ async function prepareBonsaiGeneration(input) {
4294
+ const state = await verifyBonsaiTextModel(input.model, { path: input.path });
4295
+ if (!state.verified) throw new AIError("model_unavailable", `${input.model.id} is not prepared. Call prepareTextModel() before generation.`);
4296
+ const tokenizer = new Tokenizer_default(await files.file(state.path, "tokenizer.json").json(), await files.file(state.path, "tokenizer_config.json").json());
4297
+ const inputIds = tokenizer.encode(formatMessages(messagesFromInput(input))).ids.map(Number);
4298
+ if (!inputIds.length || inputIds.some((token) => !Number.isInteger(token))) throw new AIError("invalid_message", "Tokenizer returned invalid token ids");
4299
+ return {
4300
+ request: {
4301
+ id: input.id,
4302
+ model: input.model.id,
4303
+ modelPath: state.path,
4304
+ inputIds,
4305
+ endTokenIds: endTokenIds(input.model.id),
4306
+ maxTokens: input.maxTokens,
4307
+ temperature: input.temperature,
4308
+ seed: input.seed
4309
+ },
4310
+ decode(tokenIds) {
4311
+ return tokenIds.length ? tokenizer.decode(tokenIds, { skip_special_tokens: true }) : "";
4312
+ }
4313
+ };
4314
+ }
4315
+ function messagesFromInput(input) {
4316
+ if (input.messages) {
4317
+ if (input.system !== void 0 || input.prompt !== void 0) throw new AIError("invalid_message", "messages cannot be combined with system or prompt");
4318
+ if (!input.messages.length) throw new AIError("invalid_message", "Expected at least one message");
4319
+ return input.messages;
4320
+ }
4321
+ if (!input.prompt?.trim()) throw new AIError("invalid_message", "Expected prompt");
4322
+ return [...input.system ? [{
4323
+ role: "system",
4324
+ content: input.system
4325
+ }] : [], {
4326
+ role: "user",
4327
+ content: input.prompt
4328
+ }];
4329
+ }
4330
+ function formatMessages(messages) {
4331
+ if (messages[0]?.role === "assistant") throw new AIError("invalid_message", "Conversation cannot begin with assistant");
4332
+ const parts = messages.map((message, index) => {
4333
+ if (!message.content.trim()) throw new AIError("invalid_message", `Message ${index + 1} is empty`);
4334
+ if (message.role === "system" && index !== 0) throw new AIError("invalid_message", "System message must be first");
4335
+ return `<|im_start|>${message.role}\n${message.content}<|im_end|>\n`;
4336
+ });
4337
+ parts.push("<|im_start|>assistant\n<think>\n\n</think>\n\n");
4338
+ return parts.join("");
4339
+ }
4340
+ function endTokenIds(model) {
4341
+ return model === "ternary-bonsai-27b" ? [248044, 248046] : [151643, 151645];
4342
+ }
4343
+
3886
4344
  //#endregion
3887
4345
  //#region src/ai/text.ts
3888
4346
  function promptFromInput(options) {
@@ -3893,14 +4351,27 @@ function promptFromInput(options) {
3893
4351
  if (parts.length === 0) throw new AIError("invalid_message", "Expected prompt or input");
3894
4352
  return parts.join("\n\n");
3895
4353
  }
3896
- async function isAvailable() {
4354
+ async function isAvailable(model, options = {}) {
4355
+ if (model) return (await availability(model, options)).available;
3897
4356
  try {
3898
4357
  return await bridge().isAvailable?.() ?? false;
3899
4358
  } catch {
3900
4359
  return false;
3901
4360
  }
3902
4361
  }
3903
- async function availability() {
4362
+ async function availability(model, options = {}) {
4363
+ if (model) {
4364
+ const state = await verifyBonsaiTextModel(model, options);
4365
+ if (!state.installed) return {
4366
+ available: false,
4367
+ reason: "not_installed"
4368
+ };
4369
+ if (!state.verified) return {
4370
+ available: false,
4371
+ reason: "invalid_model"
4372
+ };
4373
+ return { available: true };
4374
+ }
3904
4375
  try {
3905
4376
  return await bridge().availability?.() ?? {
3906
4377
  available: false,
@@ -3970,11 +4441,28 @@ async function generate(options) {
3970
4441
  const output = options.output;
3971
4442
  const id = generationId();
3972
4443
  const native = bridge();
3973
- const toolSubscription = listenForTools(id, options.tools);
4444
+ const toolSubscription = listenForTools(id, "tools" in options ? options.tools : void 0);
3974
4445
  const abort = () => void native.cancelTextGeneration?.(id);
3975
4446
  options.signal?.addEventListener("abort", abort, { once: true });
3976
4447
  try {
3977
4448
  if (options.signal?.aborted) throw new AIError("generation_canceled", "Generation canceled");
4449
+ if (options.model) {
4450
+ const prepared = await prepareBonsaiGeneration({
4451
+ id,
4452
+ model: options.model,
4453
+ path: options.path,
4454
+ system: options.system,
4455
+ prompt: options.prompt,
4456
+ messages: options.messages,
4457
+ temperature: options.temperature,
4458
+ maxTokens: options.maxTokens,
4459
+ seed: options.seed
4460
+ });
4461
+ if (options.signal?.aborted) throw new AIError("generation_canceled", "Generation canceled");
4462
+ const tokenIds = await native.generateLocalText?.(prepared.request);
4463
+ if (!tokenIds) throw new AIError("unsupported_runtime", "Local text generation is unavailable in this runtime");
4464
+ return prepared.decode(tokenIds);
4465
+ }
3978
4466
  const value = await native.generate?.(nativeRequest(options, id));
3979
4467
  if (output) {
3980
4468
  const result = output["~standard"].validate(value);
@@ -3992,6 +4480,10 @@ async function generate(options) {
3992
4480
  }
3993
4481
  }
3994
4482
  async function* stream(options) {
4483
+ if (options.model) {
4484
+ yield* streamBonsai(options);
4485
+ return;
4486
+ }
3995
4487
  const id = generationId();
3996
4488
  const native = bridge();
3997
4489
  const updates = [];
@@ -4029,6 +4521,56 @@ async function* stream(options) {
4029
4521
  toolSubscription?.remove();
4030
4522
  }
4031
4523
  }
4524
+ async function* streamBonsai(options) {
4525
+ const id = generationId();
4526
+ const native = bridge();
4527
+ if (options.signal?.aborted) throw new AIError("generation_canceled", "Generation canceled");
4528
+ const prepared = await prepareBonsaiGeneration({
4529
+ id,
4530
+ model: options.model,
4531
+ path: options.path,
4532
+ system: options.system,
4533
+ prompt: options.prompt,
4534
+ messages: options.messages,
4535
+ temperature: options.temperature,
4536
+ maxTokens: options.maxTokens,
4537
+ seed: options.seed
4538
+ });
4539
+ if (options.signal?.aborted) throw new AIError("generation_canceled", "Generation canceled");
4540
+ const updates = [];
4541
+ let wake;
4542
+ let settled = false;
4543
+ let failure;
4544
+ const updateSubscription = native.addListener?.("localTextGenerationUpdate", (event) => {
4545
+ if (event.id !== id) return;
4546
+ updates.push(event.tokenIds);
4547
+ wake?.();
4548
+ wake = void 0;
4549
+ });
4550
+ const abort = () => void native.cancelTextGeneration?.(id);
4551
+ options.signal?.addEventListener("abort", abort, { once: true });
4552
+ const completion = native.streamLocalText?.(prepared.request).catch((error) => {
4553
+ failure = error;
4554
+ return [];
4555
+ }).finally(() => {
4556
+ settled = true;
4557
+ wake?.();
4558
+ wake = void 0;
4559
+ });
4560
+ try {
4561
+ if (!completion) throw new AIError("unsupported_runtime", "Local text streaming is unavailable in this runtime");
4562
+ while (!settled || updates.length > 0) if (updates.length > 0) yield prepared.decode(updates.shift());
4563
+ else await new Promise((resolve) => {
4564
+ wake = resolve;
4565
+ });
4566
+ await completion;
4567
+ if (failure) throw mapNativeError(failure);
4568
+ } finally {
4569
+ if (!settled) await native.cancelTextGeneration?.(id);
4570
+ options.signal?.removeEventListener("abort", abort);
4571
+ updateSubscription?.remove();
4572
+ }
4573
+ }
4032
4574
 
4033
4575
  //#endregion
4034
4576
  //#region src/ai/text-hook.ts
@@ -4099,4 +4641,4 @@ function useTextGeneration() {
4099
4641
  }
4100
4642
 
4101
4643
  //#endregion
4102
- export { AIError, ImageGenerationError, availability, buildImageGenerationRequest, clampOutpaintRect, defaultImageLongSide, deleteImageLoRA, deleteImageModel, flux2Klein4B8BitAbliterated, fluxImageModelPaths, generate, generateImage, generateImages, getImageGenerationJob, imageMemoryTelemetry, imageModelManifest, imageModelManifests, imageSizeForAspectRatio, imageSizeForPreset, initialOutpaintRect, isAvailable, listImageGenerationJobs, listImageLoRAs, listImageModels, normalizeImageSize, prepareImageLoRA, prepareImageModel, rectForSourceScale, roundImageDimension, roundedImageSize, scheduleImageGeneration, shape, stream, unloadImageModel, upscaleImage, useImageComposer, useImageGeneration, useImageModel, useTextGeneration, verifyImageLoRA, verifyImageModel };
4644
+ export { AIError, ImageGenerationError, availability, bonsai, bonsaiTextModelCatalog, buildImageGenerationRequest, clampOutpaintRect, defaultImageLongSide, deleteImageLoRA, deleteImageModel, deleteBonsaiTextModel as deleteTextModel, flux2Klein4B8BitAbliterated, fluxImageModelPaths, generate, generateImage, generateImages, getImageGenerationJob, imageMemoryTelemetry, imageModelManifest, imageModelManifests, imageSizeForAspectRatio, imageSizeForPreset, initialOutpaintRect, isAvailable, listImageGenerationJobs, listImageLoRAs, listImageModels, listBonsaiTextModels as listTextModels, normalizeImageSize, prepareImageLoRA, prepareImageModel, prepareBonsaiTextModel as prepareTextModel, rectForSourceScale, roundImageDimension, roundedImageSize, scheduleImageGeneration, shape, stream, ternaryBonsaiImage4B, unloadImageModel, upscaleImage, useImageComposer, useImageGeneration, useImageModel, useTextGeneration, verifyImageLoRA, verifyImageModel, verifyBonsaiTextModel as verifyTextModel };