@simulatte/doppler 0.1.6 → 0.1.8

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