@simulatte/doppler 0.1.6 → 0.1.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 (316) hide show
  1. package/CHANGELOG.md +126 -0
  2. package/README.md +16 -23
  3. package/package.json +14 -1
  4. package/src/adapters/adapter-registry.js +12 -1
  5. package/src/adapters/lora-loader.js +23 -6
  6. package/src/bridge/extension-client.d.ts +5 -0
  7. package/src/bridge/extension-client.js +40 -0
  8. package/src/bridge/index.d.ts +2 -1
  9. package/src/bridge/index.js +6 -4
  10. package/src/browser/browser-converter.js +26 -1
  11. package/src/browser/file-picker.js +6 -0
  12. package/src/browser/safetensors-parser-browser.js +84 -1
  13. package/src/browser/shard-io-browser.js +2 -2
  14. package/src/browser/tensor-source-download.js +8 -2
  15. package/src/browser/tensor-source-http.d.ts +1 -0
  16. package/src/browser/tensor-source-http.js +5 -1
  17. package/src/client/doppler-api.browser.js +20 -4
  18. package/src/client/doppler-api.js +19 -3
  19. package/src/client/doppler-provider/generation.js +12 -0
  20. package/src/client/doppler-provider/model-manager.d.ts +10 -0
  21. package/src/client/doppler-provider/model-manager.js +91 -19
  22. package/src/client/doppler-provider/source-runtime.d.ts +2 -1
  23. package/src/client/doppler-provider/source-runtime.js +132 -13
  24. package/src/client/doppler-registry.json +8 -7
  25. package/src/config/backward-registry-loader.js +17 -2
  26. package/src/config/execution-v0-contract-check.js +113 -15
  27. package/src/config/kernel-path-contract-check.js +57 -29
  28. package/src/config/kernel-path-loader.js +5 -36
  29. package/src/config/kernels/kernel-ref-digests.js +1 -1
  30. package/src/config/kernels/registry.js +14 -1
  31. package/src/config/kernels/registry.json +7 -5
  32. package/src/config/loader.d.ts +1 -1
  33. package/src/config/loader.js +12 -2
  34. package/src/config/merge-contract-check.js +59 -4
  35. package/src/config/merge-helpers.js +128 -7
  36. package/src/config/merge.d.ts +1 -0
  37. package/src/config/merge.js +10 -0
  38. package/src/config/param-validator.js +47 -2
  39. package/src/config/presets/kernel-paths/{gemma2-q4k-dequant-f32a.json → gemma2-q4k-dequant-f32a-nosubgroups.json} +3 -3
  40. package/src/config/presets/kernel-paths/gemma3-f16-fused-f32a-online-streamingprefill.json +223 -0
  41. package/src/config/presets/kernel-paths/{gemma3-q4k-dequant-f32a.json → gemma3-q4k-dequant-f32a-nosubgroups.json} +3 -3
  42. package/src/config/presets/kernel-paths/registry.json +29 -8
  43. package/src/config/presets/models/gemma2.json +2 -2
  44. package/src/config/presets/runtime/experiments/bench/gemma3-bench-q4k.json +1 -1
  45. package/src/config/presets/runtime/experiments/debug/gemma3-debug-q4k.json +1 -1
  46. package/src/config/presets/runtime/experiments/verify/gemma3-verify.json +1 -1
  47. package/src/config/presets/runtime/kernels/dequant-f16-q4k.json +6 -13
  48. package/src/config/presets/runtime/kernels/dequant-f32-q4k.json +6 -13
  49. package/src/config/presets/runtime/kernels/embeddinggemma-q4k-dequant-f32a.json +37 -0
  50. package/src/config/presets/runtime/kernels/fused-q4k.json +6 -13
  51. package/src/config/presets/runtime/kernels/gemma2-q4k-dequant-f16a.json +33 -0
  52. package/src/config/presets/runtime/kernels/gemma2-q4k-dequant-f32a-nosubgroups.json +33 -0
  53. package/src/config/presets/runtime/kernels/gemma2-q4k-fused-f32a.json +33 -0
  54. package/src/config/presets/runtime/kernels/safe-q4k.json +6 -13
  55. package/src/config/presets/runtime/platform/metal-apple-q4k.json +1 -1
  56. package/src/config/runtime.js +6 -1
  57. package/src/config/schema/debug.schema.d.ts +5 -0
  58. package/src/config/schema/doppler.schema.js +16 -21
  59. package/src/config/schema/inference-defaults.schema.js +3 -3
  60. package/src/config/schema/kernel-path.schema.d.ts +5 -1
  61. package/src/config/schema/kernel-thresholds.schema.js +12 -4
  62. package/src/config/schema/manifest.schema.d.ts +2 -1
  63. package/src/config/schema/manifest.schema.js +16 -3
  64. package/src/config/training-defaults.js +30 -22
  65. package/src/converter/conversion-plan.js +94 -9
  66. package/src/converter/core.d.ts +7 -0
  67. package/src/converter/core.js +14 -9
  68. package/src/converter/execution-v0-manifest.js +4 -1
  69. package/src/converter/index.d.ts +1 -0
  70. package/src/converter/index.js +1 -0
  71. package/src/converter/manifest-inference.js +43 -12
  72. package/src/converter/parsers/diffusion.js +0 -3
  73. package/src/converter/quantization-info.js +35 -15
  74. package/src/converter/shard-packer.d.ts +1 -1
  75. package/src/converter/shard-packer.js +4 -1
  76. package/src/debug/config.js +123 -11
  77. package/src/debug/signals.js +7 -1
  78. package/src/debug/tensor.d.ts +2 -0
  79. package/src/debug/tensor.js +13 -2
  80. package/src/distribution/p2p-control-plane.js +52 -12
  81. package/src/distribution/p2p-observability.js +43 -7
  82. package/src/distribution/p2p-webrtc-browser.js +20 -0
  83. package/src/distribution/shard-delivery.js +77 -26
  84. package/src/formats/gguf/types.js +33 -16
  85. package/src/formats/rdrr/groups.d.ts +12 -4
  86. package/src/formats/rdrr/groups.js +3 -6
  87. package/src/formats/rdrr/parsing.js +39 -2
  88. package/src/formats/rdrr/types.d.ts +2 -1
  89. package/src/gpu/command-recorder.js +86 -61
  90. package/src/gpu/device.d.ts +1 -0
  91. package/src/gpu/device.js +73 -19
  92. package/src/gpu/kernel-tuner/benchmarks.js +326 -316
  93. package/src/gpu/kernel-tuner/cache.js +71 -4
  94. package/src/gpu/kernel-tuner/tuner.js +22 -4
  95. package/src/gpu/kernels/attention.js +15 -34
  96. package/src/gpu/kernels/backward/adam.js +62 -58
  97. package/src/gpu/kernels/backward/attention_backward.js +257 -169
  98. package/src/gpu/kernels/backward/conv2d_backward.js +14 -1
  99. package/src/gpu/kernels/cast.js +191 -149
  100. package/src/gpu/kernels/check-stop.js +33 -44
  101. package/src/gpu/kernels/conv2d.js +27 -17
  102. package/src/gpu/kernels/cross_entropy_loss.js +21 -15
  103. package/src/gpu/kernels/depthwise_conv2d.js +36 -26
  104. package/src/gpu/kernels/dequant.js +178 -126
  105. package/src/gpu/kernels/energy.d.ts +3 -21
  106. package/src/gpu/kernels/energy.js +111 -88
  107. package/src/gpu/kernels/feature-check.js +1 -1
  108. package/src/gpu/kernels/fused_ffn.js +84 -65
  109. package/src/gpu/kernels/fused_matmul_residual.js +56 -33
  110. package/src/gpu/kernels/fused_matmul_rmsnorm.js +62 -45
  111. package/src/gpu/kernels/gather.js +33 -15
  112. package/src/gpu/kernels/gelu.js +19 -11
  113. package/src/gpu/kernels/grouped_pointwise_conv2d.js +33 -23
  114. package/src/gpu/kernels/groupnorm.js +34 -23
  115. package/src/gpu/kernels/kv-quantize.js +5 -2
  116. package/src/gpu/kernels/layernorm.js +35 -19
  117. package/src/gpu/kernels/logit-merge.js +5 -3
  118. package/src/gpu/kernels/matmul.js +58 -39
  119. package/src/gpu/kernels/modulate.js +23 -15
  120. package/src/gpu/kernels/moe.js +221 -175
  121. package/src/gpu/kernels/pixel_shuffle.js +22 -14
  122. package/src/gpu/kernels/relu.js +18 -10
  123. package/src/gpu/kernels/repeat_channels.js +25 -17
  124. package/src/gpu/kernels/residual.js +37 -27
  125. package/src/gpu/kernels/rmsnorm.js +57 -41
  126. package/src/gpu/kernels/rope.js +3 -0
  127. package/src/gpu/kernels/sample.js +27 -38
  128. package/src/gpu/kernels/sana_linear_attention.js +18 -10
  129. package/src/gpu/kernels/scale.js +18 -11
  130. package/src/gpu/kernels/shader-cache.js +4 -2
  131. package/src/gpu/kernels/silu.js +120 -72
  132. package/src/gpu/kernels/softmax.js +44 -25
  133. package/src/gpu/kernels/split_qkv.js +23 -13
  134. package/src/gpu/kernels/transpose.js +18 -10
  135. package/src/gpu/kernels/transpose.wgsl +5 -3
  136. package/src/gpu/kernels/upsample2d.js +21 -13
  137. package/src/gpu/kernels/utils.js +20 -13
  138. package/src/gpu/partitioned-buffer-pool.js +10 -2
  139. package/src/gpu/perf-guards.js +2 -9
  140. package/src/gpu/profiler.js +27 -22
  141. package/src/gpu/readback-utils.d.ts +16 -0
  142. package/src/gpu/readback-utils.js +41 -0
  143. package/src/gpu/submit-tracker.js +13 -0
  144. package/src/gpu/uniform-cache.d.ts +1 -0
  145. package/src/gpu/uniform-cache.js +30 -9
  146. package/src/hotswap/intent-bundle.js +6 -0
  147. package/src/hotswap/manifest.d.ts +10 -1
  148. package/src/hotswap/manifest.js +12 -2
  149. package/src/hotswap/runtime.js +30 -8
  150. package/src/index-browser.d.ts +44 -0
  151. package/src/index-browser.js +14 -0
  152. package/src/inference/browser-harness-contract-helpers.d.ts +5 -0
  153. package/src/inference/browser-harness-contract-helpers.js +28 -0
  154. package/src/inference/browser-harness-diffusion-energy-suites.d.ts +2 -0
  155. package/src/inference/browser-harness-diffusion-energy-suites.js +269 -0
  156. package/src/inference/browser-harness-model-helpers.d.ts +16 -0
  157. package/src/inference/browser-harness-model-helpers.js +217 -0
  158. package/src/inference/browser-harness-report-helpers.d.ts +7 -0
  159. package/src/inference/browser-harness-report-helpers.js +42 -0
  160. package/src/inference/browser-harness-runtime-helpers.d.ts +61 -0
  161. package/src/inference/browser-harness-runtime-helpers.js +415 -0
  162. package/src/inference/browser-harness-suite-helpers.d.ts +28 -0
  163. package/src/inference/browser-harness-suite-helpers.js +268 -0
  164. package/src/inference/browser-harness-text-helpers.d.ts +27 -0
  165. package/src/inference/browser-harness-text-helpers.js +788 -0
  166. package/src/inference/browser-harness.d.ts +6 -0
  167. package/src/inference/browser-harness.js +130 -1996
  168. package/src/inference/kv-cache/base.js +140 -94
  169. package/src/inference/kv-cache/tiered.js +5 -3
  170. package/src/inference/moe-router.js +88 -56
  171. package/src/inference/multi-model-network.js +5 -3
  172. package/src/inference/network-evolution.d.ts +11 -2
  173. package/src/inference/network-evolution.js +20 -21
  174. package/src/inference/pipelines/context.d.ts +3 -0
  175. package/src/inference/pipelines/context.js +142 -2
  176. package/src/inference/pipelines/diffusion/helpers.js +7 -2
  177. package/src/inference/pipelines/diffusion/pipeline.js +2 -1
  178. package/src/inference/pipelines/diffusion/sd3-transformer.js +10 -10
  179. package/src/inference/pipelines/diffusion/vae.js +3 -7
  180. package/src/inference/pipelines/energy/pipeline.js +27 -21
  181. package/src/inference/pipelines/energy/quintel.d.ts +5 -0
  182. package/src/inference/pipelines/energy/quintel.js +11 -0
  183. package/src/inference/pipelines/energy-head/row-head-pipeline.js +17 -13
  184. package/src/inference/pipelines/structured/json-head-pipeline.js +26 -11
  185. package/src/inference/pipelines/text/attention/projections.js +151 -101
  186. package/src/inference/pipelines/text/attention/record.js +62 -8
  187. package/src/inference/pipelines/text/attention/run.js +62 -8
  188. package/src/inference/pipelines/text/config.js +3 -4
  189. package/src/inference/pipelines/text/embed.js +2 -8
  190. package/src/inference/pipelines/text/execution-plan.js +41 -19
  191. package/src/inference/pipelines/text/execution-v0-contract-helpers.d.ts +59 -0
  192. package/src/inference/pipelines/text/execution-v0-contract-helpers.js +937 -0
  193. package/src/inference/pipelines/text/execution-v0-runtime-builders.d.ts +15 -0
  194. package/src/inference/pipelines/text/execution-v0-runtime-builders.js +279 -0
  195. package/src/inference/pipelines/text/execution-v0.js +62 -1013
  196. package/src/inference/pipelines/text/generator-steps.d.ts +46 -0
  197. package/src/inference/pipelines/text/generator-steps.js +298 -207
  198. package/src/inference/pipelines/text/generator.js +6 -23
  199. package/src/inference/pipelines/text/init.js +78 -20
  200. package/src/inference/pipelines/text/kernel-path-auto-select.js +2 -0
  201. package/src/inference/pipelines/text/kernel-trace.d.ts +2 -0
  202. package/src/inference/pipelines/text/kernel-trace.js +6 -0
  203. package/src/inference/pipelines/text/layer.js +3 -9
  204. package/src/inference/pipelines/text/linear-attention.d.ts +10 -0
  205. package/src/inference/pipelines/text/linear-attention.js +80 -6
  206. package/src/inference/pipelines/text/logits/gpu.js +10 -5
  207. package/src/inference/pipelines/text/logits/index.js +10 -11
  208. package/src/inference/pipelines/text/logits/utils.d.ts +7 -0
  209. package/src/inference/pipelines/text/logits/utils.js +9 -0
  210. package/src/inference/pipelines/text/lora-apply.js +50 -32
  211. package/src/inference/pipelines/text/model-load.js +279 -104
  212. package/src/inference/pipelines/text/moe-cache.js +5 -4
  213. package/src/inference/pipelines/text/moe-cpu-gptoss.js +74 -69
  214. package/src/inference/pipelines/text/moe-cpu.js +42 -38
  215. package/src/inference/pipelines/text/moe-gpu.js +110 -86
  216. package/src/inference/pipelines/text/ops.js +90 -90
  217. package/src/inference/pipelines/text/probes.js +9 -9
  218. package/src/inference/pipelines/text/weights.js +17 -7
  219. package/src/inference/pipelines/text.js +13 -1
  220. package/src/inference/speculative.d.ts +2 -2
  221. package/src/inference/speculative.js +4 -18
  222. package/src/inference/test-harness.d.ts +1 -1
  223. package/src/inference/test-harness.js +15 -5
  224. package/src/inference/tokenizer.d.ts +0 -5
  225. package/src/inference/tokenizer.js +4 -23
  226. package/src/inference/tokenizers/bpe.js +9 -0
  227. package/src/inference/tokenizers/bundled.js +20 -0
  228. package/src/inference/tokenizers/sentencepiece.js +12 -0
  229. package/src/loader/doppler-loader.js +38 -22
  230. package/src/loader/dtype-utils.js +3 -44
  231. package/src/loader/embedding-loader.js +7 -3
  232. package/src/loader/experts/expert-cache.js +13 -6
  233. package/src/loader/experts/expert-loader.js +10 -6
  234. package/src/loader/final-weights-loader.js +8 -4
  235. package/src/loader/layer-loader.js +2 -1
  236. package/src/loader/loader-state.js +2 -2
  237. package/src/loader/memory-monitor.js +8 -0
  238. package/src/loader/multi-model-loader.d.ts +14 -0
  239. package/src/loader/multi-model-loader.js +70 -24
  240. package/src/loader/shard-cache.js +81 -12
  241. package/src/loader/shard-resolver.js +25 -3
  242. package/src/loader/tensors/tensor-loader.js +209 -144
  243. package/src/loader/tensors/tensor-reader.js +76 -19
  244. package/src/loader/weight-downcast.js +1 -1
  245. package/src/memory/buffer-pool.d.ts +9 -1
  246. package/src/memory/buffer-pool.js +109 -44
  247. package/src/memory/unified-detect.js +1 -1
  248. package/src/rules/inference/kernel-path.rules.json +24 -8
  249. package/src/rules/rule-registry.js +25 -1
  250. package/src/storage/backends/opfs-store.js +68 -24
  251. package/src/storage/downloader.js +364 -83
  252. package/src/storage/index.d.ts +3 -0
  253. package/src/storage/index.js +3 -0
  254. package/src/storage/preflight.d.ts +2 -2
  255. package/src/storage/preflight.js +24 -2
  256. package/src/storage/quickstart-downloader.js +11 -5
  257. package/src/storage/registry.js +10 -4
  258. package/src/storage/reports.js +1 -1
  259. package/src/storage/shard-manager.d.ts +15 -1
  260. package/src/storage/shard-manager.js +51 -3
  261. package/src/storage/source-artifact-store.d.ts +52 -0
  262. package/src/storage/source-artifact-store.js +234 -0
  263. package/src/tooling/command-api-constants.d.ts +9 -0
  264. package/src/tooling/command-api-constants.js +9 -0
  265. package/src/tooling/command-api-family-normalizers.d.ts +9 -0
  266. package/src/tooling/command-api-family-normalizers.js +343 -0
  267. package/src/tooling/command-api-helpers.d.ts +25 -0
  268. package/src/tooling/command-api-helpers.js +262 -0
  269. package/src/tooling/command-api.js +16 -602
  270. package/src/tooling/command-envelope.js +4 -1
  271. package/src/tooling/command-runner-shared.js +52 -18
  272. package/src/tooling/lean-execution-contract.js +150 -3
  273. package/src/tooling/node-browser-command-runner.js +161 -271
  274. package/src/tooling/node-command-runner.js +29 -3
  275. package/src/tooling/node-converter.js +27 -1
  276. package/src/tooling/node-source-runtime.d.ts +1 -1
  277. package/src/tooling/node-source-runtime.js +84 -3
  278. package/src/tooling/node-webgpu.js +24 -21
  279. package/src/tooling/opfs-cache.js +21 -4
  280. package/src/tooling/runtime-input-composition.d.ts +38 -0
  281. package/src/tooling/runtime-input-composition.js +86 -0
  282. package/src/tooling/source-runtime-bundle.d.ts +40 -5
  283. package/src/tooling/source-runtime-bundle.js +261 -34
  284. package/src/tooling/source-runtime-materializer.d.ts +6 -0
  285. package/src/tooling/source-runtime-materializer.js +93 -0
  286. package/src/training/attention-backward.js +32 -17
  287. package/src/training/autograd.js +80 -52
  288. package/src/training/checkpoint-watch.d.ts +2 -1
  289. package/src/training/checkpoint-watch.js +39 -6
  290. package/src/training/checkpoint.js +40 -11
  291. package/src/training/clip.js +2 -1
  292. package/src/training/datasets/token-batch.js +20 -8
  293. package/src/training/distillation/checkpoint-watch.js +1 -0
  294. package/src/training/distillation/student-fixture.d.ts +22 -0
  295. package/src/training/distillation/student-fixture.js +846 -0
  296. package/src/training/distillation/suite-data.d.ts +45 -0
  297. package/src/training/distillation/suite-data.js +189 -0
  298. package/src/training/lora-pipeline.js +4 -7
  299. package/src/training/lora.js +26 -12
  300. package/src/training/loss.js +5 -6
  301. package/src/training/objectives/cross_entropy.js +2 -5
  302. package/src/training/objectives/distill_kd.js +4 -8
  303. package/src/training/objectives/distill_triplet.js +4 -8
  304. package/src/training/objectives/ul_stage2_base.js +4 -8
  305. package/src/training/operator-command.js +2 -0
  306. package/src/training/optimizer.js +19 -7
  307. package/src/training/runner.js +2 -1
  308. package/src/training/suite.js +18 -978
  309. package/src/training/tensor-factory.d.ts +9 -0
  310. package/src/training/tensor-factory.js +13 -0
  311. package/src/training/trainer.js +3 -5
  312. package/src/training/ul_dataset.js +3 -5
  313. package/src/training/workloads.js +70 -79
  314. package/src/version.js +1 -1
  315. package/tools/convert-safetensors-node.js +22 -16
  316. package/tools/doppler-cli.js +44 -25
@@ -7,10 +7,14 @@ import {
7
7
 
8
8
  import {
9
9
  openModelStore,
10
+ createFileWriter,
11
+ createStreamingHasher,
10
12
  writeShard,
11
13
  shardExists,
12
14
  loadShard,
15
+ loadFileFromStore,
13
16
  deleteShard,
17
+ deleteFileFromStore,
14
18
  saveManifest,
15
19
  saveTokenizer,
16
20
  saveTokenizerModel,
@@ -40,6 +44,7 @@ import {
40
44
  } from './download-types.js';
41
45
 
42
46
  import { downloadShard as downloadShardFromDistribution } from '../distribution/shard-delivery.js';
47
+ import { resolveSourceArtifact, normalizeSourceArtifactPath } from './source-artifact-store.js';
43
48
 
44
49
  // ============================================================================
45
50
  // Module State
@@ -51,6 +56,7 @@ let db = null;
51
56
  const activeDownloads = new Map();
52
57
 
53
58
  function buildManifestVersionSet(manifest) {
59
+ const sourceArtifact = resolveSourceArtifact(manifest);
54
60
  if (!manifest || typeof manifest !== 'object') return 'manifest:invalid';
55
61
  const shards = Array.isArray(manifest.shards)
56
62
  ? manifest.shards.map((shard, index) => ({
@@ -67,6 +73,7 @@ function buildManifestVersionSet(manifest) {
67
73
  tensorCount: manifest.tensorCount ?? null,
68
74
  totalSize: manifest.totalSize ?? null,
69
75
  shards,
76
+ sourceRuntime: sourceArtifact?.sourceRuntime ?? null,
70
77
  };
71
78
  return JSON.stringify(payload);
72
79
  }
@@ -93,6 +100,31 @@ function normalizeSourceStats(value) {
93
100
  };
94
101
  }
95
102
 
103
+ function isTokenizerJsonRequired(tokenizer) {
104
+ return Boolean(
105
+ tokenizer
106
+ && (tokenizer.type === 'bundled' || tokenizer.type === 'huggingface')
107
+ && typeof tokenizer.file === 'string'
108
+ && tokenizer.file.length > 0
109
+ );
110
+ }
111
+
112
+ function getTokenizerModelPath(tokenizer) {
113
+ if (!tokenizer || typeof tokenizer !== 'object') {
114
+ return null;
115
+ }
116
+ const explicit = typeof tokenizer.sentencepieceModel === 'string'
117
+ ? tokenizer.sentencepieceModel
118
+ : null;
119
+ if (explicit && explicit.length > 0) {
120
+ return explicit;
121
+ }
122
+ if (tokenizer.type === 'sentencepiece') {
123
+ return 'tokenizer.model';
124
+ }
125
+ return null;
126
+ }
127
+
96
128
  // ============================================================================
97
129
  // IndexedDB Operations
98
130
  // ============================================================================
@@ -230,6 +262,12 @@ function isDatabaseClosingError(error) {
230
262
  || (error)?.name === 'InvalidStateError';
231
263
  }
232
264
 
265
+ function createAbortError(message = 'Download aborted') {
266
+ const error = new Error(message);
267
+ error.name = 'AbortError';
268
+ return error;
269
+ }
270
+
233
271
  // ============================================================================
234
272
  // Fetch Operations
235
273
  // ============================================================================
@@ -276,6 +314,106 @@ async function fetchWithRetry(url, options = {}) {
276
314
  throw (lastError);
277
315
  }
278
316
 
317
+ function joinArtifactUrl(baseUrl, relativePath) {
318
+ const root = String(baseUrl || '').trim();
319
+ const rel = normalizeSourceArtifactPath(relativePath);
320
+ if (!root || !rel) {
321
+ throw new Error('joinArtifactUrl requires baseUrl and relativePath.');
322
+ }
323
+ return new URL(rel, root.endsWith('/') ? root : `${root}/`).href;
324
+ }
325
+
326
+ async function fileExistsInStore(path) {
327
+ try {
328
+ await loadFileFromStore(path);
329
+ return true;
330
+ } catch (error) {
331
+ const message = String(error?.message || '');
332
+ return error?.name === 'NotFoundError' || message.toLowerCase().includes('not found')
333
+ ? false
334
+ : Promise.reject(error);
335
+ }
336
+ }
337
+
338
+ async function computeAssetHash(payload, algorithm = 'sha256') {
339
+ const bytes = payload instanceof Uint8Array ? payload : new Uint8Array(payload);
340
+ const hasher = await createStreamingHasher(String(algorithm || 'sha256').trim().toLowerCase());
341
+ hasher.update(bytes);
342
+ const digest = await hasher.finalize();
343
+ return Array.from(digest)
344
+ .map((byte) => byte.toString(16).padStart(2, '0'))
345
+ .join('');
346
+ }
347
+
348
+ async function downloadSourceAsset(url, asset, options = {}) {
349
+ const response = await fetchWithRetry(url, { signal: options.signal });
350
+ const expectedSize = Number.isFinite(asset?.size) ? Math.max(0, Math.floor(Number(asset.size))) : null;
351
+ const expectedHash = typeof asset?.hash === 'string' && asset.hash.trim() ? asset.hash.trim().toLowerCase() : null;
352
+ const hashAlgorithm = typeof asset?.hashAlgorithm === 'string' && asset.hashAlgorithm.trim()
353
+ ? asset.hashAlgorithm.trim().toLowerCase()
354
+ : 'sha256';
355
+ const writer = await createFileWriter(asset.path);
356
+ const hasher = expectedHash ? await createStreamingHasher(hashAlgorithm) : null;
357
+ let receivedBytes = 0;
358
+ try {
359
+ if (response.body && typeof response.body.getReader === 'function') {
360
+ const reader = response.body.getReader();
361
+ while (true) {
362
+ const { done, value } = await reader.read();
363
+ if (done) {
364
+ break;
365
+ }
366
+ if (!(value instanceof Uint8Array) || value.byteLength <= 0) {
367
+ continue;
368
+ }
369
+ await writer.write(value);
370
+ hasher?.update(value);
371
+ receivedBytes += value.byteLength;
372
+ options.onProgress?.(receivedBytes);
373
+ }
374
+ } else {
375
+ const bytes = new Uint8Array(await response.arrayBuffer());
376
+ await writer.write(bytes);
377
+ hasher?.update(bytes);
378
+ receivedBytes += bytes.byteLength;
379
+ options.onProgress?.(receivedBytes);
380
+ }
381
+ await writer.close();
382
+
383
+ if (expectedSize != null && receivedBytes !== expectedSize) {
384
+ throw new Error(
385
+ `Asset size mismatch for ${asset.path}: expected ${expectedSize}, got ${receivedBytes}`
386
+ );
387
+ }
388
+
389
+ if (hasher && expectedHash) {
390
+ const computedHashBytes = await hasher.finalize();
391
+ const computedHash = Array.from(computedHashBytes)
392
+ .map((byte) => byte.toString(16).padStart(2, '0'))
393
+ .join('');
394
+ if (computedHash !== expectedHash) {
395
+ throw new Error(
396
+ `Asset hash mismatch for ${asset.path}: expected ${expectedHash}, got ${computedHash}`
397
+ );
398
+ }
399
+ }
400
+
401
+ return {
402
+ source: 'http',
403
+ path: asset.path,
404
+ bytes: receivedBytes,
405
+ };
406
+ } catch (error) {
407
+ try {
408
+ await writer.abort();
409
+ } catch {}
410
+ try {
411
+ await deleteFileFromStore(asset.path);
412
+ } catch {}
413
+ throw error;
414
+ }
415
+ }
416
+
279
417
 
280
418
  async function downloadShard(
281
419
  baseUrl,
@@ -327,9 +465,14 @@ export async function downloadModel(
327
465
  const {
328
466
  concurrency = getDefaultConcurrency(),
329
467
  requestPersist = true,
330
- modelId: overrideModelId = undefined
468
+ modelId: overrideModelId = undefined,
469
+ signal: externalSignal = null,
331
470
  } = options;
332
471
 
472
+ if (externalSignal?.aborted) {
473
+ throw createAbortError();
474
+ }
475
+
333
476
  // Request persistent storage if needed
334
477
  if (requestPersist) {
335
478
  await requestPersistence();
@@ -340,14 +483,19 @@ export async function downloadModel(
340
483
  const manifestResponse = await fetchWithRetry(manifestUrl);
341
484
  const manifestJson = await manifestResponse.text();
342
485
  const manifest = parseManifest(manifestJson);
486
+ const directSourceArtifact = resolveSourceArtifact(manifest);
487
+ const trackedShards = directSourceArtifact ? directSourceArtifact.sourceFiles : manifest.shards;
488
+ const trackedTotalBytes = directSourceArtifact
489
+ ? directSourceArtifact.totalBytes
490
+ : manifest.totalSize;
343
491
 
344
492
  // Use override modelId for storage, or fall back to manifest's modelId
345
493
  const storageModelId = overrideModelId || manifest.modelId;
346
494
 
347
495
  // Check available space
348
- const spaceCheck = await checkSpaceAvailable(manifest.totalSize);
496
+ const spaceCheck = await checkSpaceAvailable(trackedTotalBytes);
349
497
  if (!spaceCheck.hasSpace) {
350
- throw new QuotaExceededError(manifest.totalSize, spaceCheck.info.available);
498
+ throw new QuotaExceededError(trackedTotalBytes, spaceCheck.info.available);
351
499
  }
352
500
 
353
501
  // Open model directory
@@ -377,7 +525,14 @@ export async function downloadModel(
377
525
  if (savedVersionSet !== manifestVersionSet) {
378
526
  log.warn('Downloader', `Manifest version-set changed for ${storageModelId}, resetting cached shards`);
379
527
  for (const idx of state.completedShards) {
380
- await deleteShard(idx);
528
+ if (directSourceArtifact) {
529
+ const sourceEntry = directSourceArtifact.sourceFiles[idx];
530
+ if (sourceEntry) {
531
+ await deleteFileFromStore(sourceEntry.path);
532
+ }
533
+ } else {
534
+ await deleteShard(idx);
535
+ }
381
536
  }
382
537
  state.completedShards.clear();
383
538
  }
@@ -389,6 +544,13 @@ export async function downloadModel(
389
544
  state.lastSourcePath = typeof state.lastSourcePath === 'string' ? state.lastSourcePath : null;
390
545
  // Check which shards actually exist (in case OPFS was cleared)
391
546
  for (const idx of state.completedShards) {
547
+ if (directSourceArtifact) {
548
+ const sourceEntry = directSourceArtifact.sourceFiles[idx];
549
+ if (!sourceEntry || !(await fileExistsInStore(sourceEntry.path))) {
550
+ state.completedShards.delete(idx);
551
+ }
552
+ continue;
553
+ }
392
554
  if (!(await shardExists(idx))) {
393
555
  state.completedShards.delete(idx);
394
556
  }
@@ -396,23 +558,50 @@ export async function downloadModel(
396
558
  // Verify hashes for completed shards; drop and re-download corrupt shards
397
559
  for (const idx of Array.from(state.completedShards)) {
398
560
  try {
399
- await loadShard(idx, { verify: true });
561
+ if (directSourceArtifact) {
562
+ const sourceEntry = directSourceArtifact.sourceFiles[idx];
563
+ if (!sourceEntry?.hash) {
564
+ continue;
565
+ }
566
+ const payload = await loadFileFromStore(sourceEntry.path);
567
+ const computedHash = await computeAssetHash(payload, sourceEntry.hashAlgorithm);
568
+ if (computedHash !== sourceEntry.hash) {
569
+ throw new Error(
570
+ `Hash mismatch for source asset ${sourceEntry.path}: expected ${sourceEntry.hash}, got ${computedHash}`
571
+ );
572
+ }
573
+ } else {
574
+ await loadShard(idx, { verify: true });
575
+ }
400
576
  } catch (err) {
401
577
  log.warn('Downloader', `Shard ${idx} failed verification, re-downloading`);
402
578
  state.completedShards.delete(idx);
403
- await deleteShard(idx);
579
+ if (directSourceArtifact) {
580
+ const sourceEntry = directSourceArtifact.sourceFiles[idx];
581
+ if (sourceEntry) {
582
+ await deleteFileFromStore(sourceEntry.path);
583
+ }
584
+ } else {
585
+ await deleteShard(idx);
586
+ }
404
587
  }
405
588
  }
406
589
  }
407
590
 
408
591
  // Create abort controller
409
592
  const abortController = new AbortController();
593
+ const abortFromExternalSignal = () => {
594
+ abortController.abort();
595
+ };
596
+ if (externalSignal && typeof externalSignal.addEventListener === 'function') {
597
+ externalSignal.addEventListener('abort', abortFromExternalSignal, { once: true });
598
+ }
410
599
  activeDownloads.set(storageModelId, {
411
600
  state,
412
601
  abortController
413
602
  });
414
603
 
415
- const totalShards = manifest.shards.length;
604
+ const totalShards = trackedShards.length;
416
605
  const requiredEncoding = getRequiredContentEncoding();
417
606
 
418
607
  const pendingShards = [];
@@ -427,7 +616,7 @@ export async function downloadModel(
427
616
  // Progress tracking
428
617
  let downloadedBytes = 0;
429
618
  for (const idx of state.completedShards) {
430
- const info = manifest.shards[idx];
619
+ const info = trackedShards[idx];
431
620
  if (info) downloadedBytes += info.size;
432
621
  }
433
622
 
@@ -464,9 +653,9 @@ export async function downloadModel(
464
653
  manifest,
465
654
  totalShards,
466
655
  completedShards: (state).completedShards.size,
467
- totalBytes: manifest.totalSize,
656
+ totalBytes: trackedTotalBytes,
468
657
  downloadedBytes,
469
- percent: (downloadedBytes / manifest.totalSize) * 100,
658
+ percent: trackedTotalBytes > 0 ? (downloadedBytes / trackedTotalBytes) * 100 : 0,
470
659
  status: (state).status,
471
660
  currentShard,
472
661
  speed: speedTracker.speed,
@@ -492,58 +681,96 @@ export async function downloadModel(
492
681
  updateProgress(shardIndex);
493
682
 
494
683
  try {
495
- const shardInfo = manifest.shards[shardIndex];
496
- if (!shardInfo) {
497
- throw new Error(`Invalid shard index: ${shardIndex}`);
498
- }
499
- const algorithm = manifest.hashAlgorithm;
500
- if (!algorithm) {
501
- throw new Error('Manifest missing hashAlgorithm for download verification.');
502
- }
503
- const expectedHash = shardInfo.hash;
504
- if (!expectedHash) {
505
- throw new Error(`Shard ${shardIndex} is missing hash in manifest`);
506
- }
507
- const expectedSize = Number.isFinite(shardInfo.size) ? Math.floor(shardInfo.size) : null;
508
- const result = await downloadShard(baseUrl, shardIndex, shardInfo, {
509
- signal: abortController.signal,
510
- algorithm,
511
- requiredEncoding,
512
- expectedHash,
513
- expectedSize,
514
- expectedManifestVersionSet: manifestVersionSet,
515
- writeToStore: true,
516
- onProgress: ( p) => {
517
- const prev = shardProgress.get(shardIndex) || 0;
518
- const delta = Math.max(0, p.receivedBytes - prev);
519
- shardProgress.set(shardIndex, p.receivedBytes);
520
- downloadedBytes += delta;
521
- updateProgress(shardIndex);
684
+ if (directSourceArtifact) {
685
+ const sourceAsset = directSourceArtifact.sourceFiles[shardIndex];
686
+ if (!sourceAsset) {
687
+ throw new Error(`Invalid source asset index: ${shardIndex}`);
522
688
  }
523
- });
689
+ const result = await downloadSourceAsset(
690
+ joinArtifactUrl(baseUrl, sourceAsset.path),
691
+ sourceAsset,
692
+ {
693
+ signal: abortController.signal,
694
+ onProgress: (receivedBytes) => {
695
+ const prev = shardProgress.get(shardIndex) || 0;
696
+ const delta = Math.max(0, receivedBytes - prev);
697
+ shardProgress.set(shardIndex, receivedBytes);
698
+ downloadedBytes += delta;
699
+ updateProgress(shardIndex);
700
+ },
701
+ }
702
+ );
524
703
 
525
- if (result.hash !== expectedHash) {
526
- await deleteShard(shardIndex);
527
- throw new Error(`Hash mismatch for shard ${shardIndex}: expected ${expectedHash}, got ${result.hash}`);
528
- }
704
+ const source = typeof result.source === 'string' ? result.source : 'unknown';
705
+ const sourceStats = normalizeSourceStats(state.sourceStats);
706
+ if (source in sourceStats) {
707
+ sourceStats[source] += 1;
708
+ } else {
709
+ sourceStats.unknown += 1;
710
+ }
711
+ state.sourceStats = sourceStats;
712
+ state.lastSource = source;
713
+ state.lastSourcePath = typeof result.path === 'string' ? result.path : null;
714
+
715
+ const observedBytes = shardProgress.get(shardIndex) || 0;
716
+ const shardBytes = sourceAsset.size ?? result.bytes ?? observedBytes;
717
+ if (shardBytes > observedBytes) {
718
+ downloadedBytes += shardBytes - observedBytes;
719
+ }
720
+ } else {
721
+ const shardInfo = manifest.shards[shardIndex];
722
+ if (!shardInfo) {
723
+ throw new Error(`Invalid shard index: ${shardIndex}`);
724
+ }
725
+ const algorithm = manifest.hashAlgorithm;
726
+ if (!algorithm) {
727
+ throw new Error('Manifest missing hashAlgorithm for download verification.');
728
+ }
729
+ const expectedHash = shardInfo.hash;
730
+ if (!expectedHash) {
731
+ throw new Error(`Shard ${shardIndex} is missing hash in manifest`);
732
+ }
733
+ const expectedSize = Number.isFinite(shardInfo.size) ? Math.floor(shardInfo.size) : null;
734
+ const result = await downloadShard(baseUrl, shardIndex, shardInfo, {
735
+ signal: abortController.signal,
736
+ algorithm,
737
+ requiredEncoding,
738
+ expectedHash,
739
+ expectedSize,
740
+ expectedManifestVersionSet: manifestVersionSet,
741
+ writeToStore: true,
742
+ onProgress: ( p) => {
743
+ const prev = shardProgress.get(shardIndex) || 0;
744
+ const delta = Math.max(0, p.receivedBytes - prev);
745
+ shardProgress.set(shardIndex, p.receivedBytes);
746
+ downloadedBytes += delta;
747
+ updateProgress(shardIndex);
748
+ }
749
+ });
529
750
 
530
- await persistDownloadedShardIfNeeded(result, shardIndex);
751
+ if (result.hash !== expectedHash) {
752
+ await deleteShard(shardIndex);
753
+ throw new Error(`Hash mismatch for shard ${shardIndex}: expected ${expectedHash}, got ${result.hash}`);
754
+ }
531
755
 
532
- const source = typeof result.source === 'string' ? result.source : 'unknown';
533
- const sourceStats = normalizeSourceStats(state.sourceStats);
534
- if (source in sourceStats) {
535
- sourceStats[source] += 1;
536
- } else {
537
- sourceStats.unknown += 1;
538
- }
539
- state.sourceStats = sourceStats;
540
- state.lastSource = source;
541
- state.lastSourcePath = typeof result.path === 'string' ? result.path : null;
542
-
543
- const observedBytes = shardProgress.get(shardIndex) || 0;
544
- const shardBytes = shardInfo.size ?? result.bytes ?? observedBytes;
545
- if (shardBytes > observedBytes) {
546
- downloadedBytes += shardBytes - observedBytes;
756
+ await persistDownloadedShardIfNeeded(result, shardIndex);
757
+
758
+ const source = typeof result.source === 'string' ? result.source : 'unknown';
759
+ const sourceStats = normalizeSourceStats(state.sourceStats);
760
+ if (source in sourceStats) {
761
+ sourceStats[source] += 1;
762
+ } else {
763
+ sourceStats.unknown += 1;
764
+ }
765
+ state.sourceStats = sourceStats;
766
+ state.lastSource = source;
767
+ state.lastSourcePath = typeof result.path === 'string' ? result.path : null;
768
+
769
+ const observedBytes = shardProgress.get(shardIndex) || 0;
770
+ const shardBytes = shardInfo.size ?? result.bytes ?? observedBytes;
771
+ if (shardBytes > observedBytes) {
772
+ downloadedBytes += shardBytes - observedBytes;
773
+ }
547
774
  }
548
775
 
549
776
  // Update state
@@ -599,6 +826,10 @@ export async function downloadModel(
599
826
  // Wait for any remaining downloads to complete
600
827
  await Promise.all([...downloadPromises]);
601
828
 
829
+ if (abortController.signal.aborted) {
830
+ throw createAbortError();
831
+ }
832
+
602
833
  // Verify all shards completed
603
834
  if (state.completedShards.size === totalShards) {
604
835
  state.status = 'completed';
@@ -606,35 +837,50 @@ export async function downloadModel(
606
837
  // Save manifest to OPFS
607
838
  await saveManifest(manifestJson);
608
839
 
609
- // Download tokenizer assets if specified
610
- const tokenizer = (manifest.tokenizer);
611
- const hasBundledTokenizer = (tokenizer?.type === 'bundled' || tokenizer?.type === 'huggingface') && tokenizer?.file;
612
- if (hasBundledTokenizer) {
613
- try {
840
+ if (directSourceArtifact) {
841
+ for (const asset of directSourceArtifact.auxiliaryFiles) {
842
+ const alreadyPresent = await fileExistsInStore(asset.path);
843
+ if (alreadyPresent) {
844
+ continue;
845
+ }
846
+ await downloadSourceAsset(joinArtifactUrl(baseUrl, asset.path), asset, {
847
+ signal: abortController.signal,
848
+ onProgress: (receivedBytes) => {
849
+ const previous = shardProgress.get(asset.path) || 0;
850
+ const delta = Math.max(0, receivedBytes - previous);
851
+ shardProgress.set(asset.path, receivedBytes);
852
+ downloadedBytes += delta;
853
+ updateProgress(null);
854
+ },
855
+ });
856
+ const observedBytes = shardProgress.get(asset.path) || 0;
857
+ shardProgress.delete(asset.path);
858
+ const assetBytes = asset.size ?? observedBytes;
859
+ if (assetBytes > observedBytes) {
860
+ downloadedBytes += assetBytes - observedBytes;
861
+ }
862
+ updateProgress(null, true);
863
+ }
864
+ } else {
865
+ // Download tokenizer assets if specified
866
+ const tokenizer = (manifest.tokenizer);
867
+ if (isTokenizerJsonRequired(tokenizer)) {
614
868
  const tokenizerUrl = `${baseUrl}/${ (tokenizer).file}`;
615
869
  log.verbose('Downloader', `Fetching bundled tokenizer from ${tokenizerUrl}`);
616
870
  const tokenizerResponse = await fetchWithRetry(tokenizerUrl);
617
871
  const tokenizerJson = await tokenizerResponse.text();
618
872
  await saveTokenizer(tokenizerJson);
619
873
  log.verbose('Downloader', 'Saved bundled tokenizer.json');
620
- } catch (err) {
621
- log.warn('Downloader', `Failed to download tokenizer.json: ${ (err).message}`);
622
- // Non-fatal - model will fall back to HuggingFace tokenizer
623
874
  }
624
- }
625
875
 
626
- const sentencepieceModel = tokenizer?.sentencepieceModel
627
- ?? (tokenizer?.type === 'sentencepiece' ? 'tokenizer.model' : null);
628
- if (sentencepieceModel) {
629
- try {
876
+ const sentencepieceModel = getTokenizerModelPath(tokenizer);
877
+ if (sentencepieceModel) {
630
878
  const modelUrl = `${baseUrl}/${sentencepieceModel}`;
631
879
  log.verbose('Downloader', `Fetching sentencepiece model from ${modelUrl}`);
632
880
  const modelResponse = await fetchWithRetry(modelUrl);
633
881
  const modelBuffer = await modelResponse.arrayBuffer();
634
882
  await saveTokenizerModel(modelBuffer);
635
883
  log.verbose('Downloader', 'Saved tokenizer.model');
636
- } catch (err) {
637
- log.warn('Downloader', `Failed to download tokenizer.model: ${ (err).message}`);
638
884
  }
639
885
  }
640
886
 
@@ -660,6 +906,9 @@ export async function downloadModel(
660
906
  throw error;
661
907
 
662
908
  } finally {
909
+ if (externalSignal && typeof externalSignal.removeEventListener === 'function') {
910
+ externalSignal.removeEventListener('abort', abortFromExternalSignal);
911
+ }
663
912
  activeDownloads.delete(storageModelId);
664
913
  }
665
914
  }
@@ -684,7 +933,10 @@ export async function resumeDownload(
684
933
  throw new Error(`No download state found for model: ${modelId}`);
685
934
  }
686
935
 
687
- return downloadModel(state.baseUrl, onProgress, options);
936
+ return downloadModel(state.baseUrl, onProgress, {
937
+ ...options,
938
+ modelId: options.modelId ?? state.modelId,
939
+ });
688
940
  }
689
941
 
690
942
 
@@ -694,11 +946,13 @@ export async function getDownloadProgress(modelId) {
694
946
  if (active) {
695
947
  const state = active.state;
696
948
  const manifest = state.manifest;
697
- const totalShards = manifest?.shards?.length || 0;
949
+ const directSourceArtifact = resolveSourceArtifact(manifest);
950
+ const trackedShards = directSourceArtifact ? directSourceArtifact.sourceFiles : (manifest?.shards || []);
951
+ const totalShards = trackedShards.length;
698
952
 
699
953
  let downloadedBytes = 0;
700
954
  for (const idx of state.completedShards) {
701
- const info = manifest?.shards?.[idx];
955
+ const info = trackedShards[idx];
702
956
  if (info) downloadedBytes += info.size;
703
957
  }
704
958
 
@@ -706,9 +960,14 @@ export async function getDownloadProgress(modelId) {
706
960
  modelId,
707
961
  totalShards,
708
962
  completedShards: state.completedShards.size,
709
- totalBytes: manifest?.totalSize || 0,
963
+ totalBytes: directSourceArtifact ? directSourceArtifact.totalBytes : (manifest?.totalSize || 0),
710
964
  downloadedBytes,
711
- percent: manifest ? (downloadedBytes / manifest.totalSize) * 100 : 0,
965
+ percent: manifest
966
+ ? (
967
+ downloadedBytes
968
+ / (directSourceArtifact ? directSourceArtifact.totalBytes : manifest.totalSize || 1)
969
+ ) * 100
970
+ : 0,
712
971
  status: state.status,
713
972
  currentShard: null,
714
973
  speed: 0,
@@ -721,20 +980,25 @@ export async function getDownloadProgress(modelId) {
721
980
  // Check saved state
722
981
  const state = await loadDownloadState(modelId);
723
982
  if (!state) return null;
983
+ const directSourceArtifact = resolveSourceArtifact(state.manifest);
984
+ const trackedShards = directSourceArtifact ? directSourceArtifact.sourceFiles : state.manifest.shards;
724
985
 
725
986
  let downloadedBytes = 0;
726
987
  for (const idx of state.completedShards) {
727
- const shard = state.manifest.shards[idx];
988
+ const shard = trackedShards[idx];
728
989
  if (shard) downloadedBytes += shard.size;
729
990
  }
730
991
 
731
992
  return {
732
993
  modelId,
733
- totalShards: state.manifest.shards.length,
994
+ totalShards: trackedShards.length,
734
995
  completedShards: state.completedShards.size,
735
- totalBytes: state.manifest.totalSize,
996
+ totalBytes: directSourceArtifact ? directSourceArtifact.totalBytes : state.manifest.totalSize,
736
997
  downloadedBytes,
737
- percent: (downloadedBytes / state.manifest.totalSize) * 100,
998
+ percent: (
999
+ downloadedBytes
1000
+ / (directSourceArtifact ? directSourceArtifact.totalBytes : state.manifest.totalSize || 1)
1001
+ ) * 100,
738
1002
  status: state.status,
739
1003
  currentShard: null,
740
1004
  speed: 0,
@@ -790,7 +1054,8 @@ export async function checkDownloadNeeded(modelId) {
790
1054
  };
791
1055
  }
792
1056
 
793
- const totalShards = state.manifest.shards.length;
1057
+ const directSourceArtifact = resolveSourceArtifact(state.manifest);
1058
+ const totalShards = directSourceArtifact ? directSourceArtifact.sourceFiles.length : state.manifest.shards.length;
794
1059
 
795
1060
  const missingShards = [];
796
1061
 
@@ -808,6 +1073,22 @@ export async function checkDownloadNeeded(modelId) {
808
1073
  };
809
1074
  }
810
1075
 
1076
+ if (directSourceArtifact) {
1077
+ const missingAuxiliaryFiles = [];
1078
+ for (const entry of directSourceArtifact.auxiliaryFiles) {
1079
+ if (!(await fileExistsInStore(entry.path))) {
1080
+ missingAuxiliaryFiles.push(entry.path);
1081
+ }
1082
+ }
1083
+ if (missingAuxiliaryFiles.length > 0) {
1084
+ return {
1085
+ needed: true,
1086
+ reason: `Missing ${missingAuxiliaryFiles.length} direct-source auxiliary file(s)`,
1087
+ missingShards: [],
1088
+ };
1089
+ }
1090
+ }
1091
+
811
1092
  return {
812
1093
  needed: false,
813
1094
  reason: 'Model fully downloaded',
@@ -20,6 +20,7 @@ export {
20
20
  writeShard,
21
21
  createShardWriter,
22
22
  createConversionShardWriter,
23
+ createFileWriter,
23
24
  loadShard,
24
25
  loadShardRange,
25
26
  streamShardRange,
@@ -32,6 +33,7 @@ export {
32
33
  listModels,
33
34
  listFilesInStore,
34
35
  loadFileFromStore,
36
+ loadFileRangeFromStore,
35
37
  streamFileFromStore,
36
38
  getModelInfo,
37
39
  modelExists,
@@ -46,6 +48,7 @@ export {
46
48
  saveAuxFile,
47
49
  loadAuxFile,
48
50
  loadAuxText,
51
+ deleteFileFromStore,
49
52
  cleanup,
50
53
  } from './shard-manager.js';
51
54
  export type {