mlx-bun 0.5.0 → 1.0.0
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.
- package/README.md +1449 -109
- package/THIRD_PARTY_NOTICES.md +882 -0
- package/bin/mlx-bun.convert.mjs +4 -0
- package/bin/mlx-bun.fuse.mjs +4 -0
- package/bin/mlx-bun.generate.mjs +4 -0
- package/bin/mlx-bun.lora.mjs +4 -0
- package/bin/mlx-bun.mjs +13 -21
- package/bin/mlx-bun.server.mjs +4 -0
- package/bin/mlx-bun.upload.mjs +4 -0
- package/dist/web/app.js +9584 -0
- package/package.json +50 -53
- package/src/cli/ane-install.ts +76 -0
- package/src/cli/args.ts +179 -0
- package/src/cli/engine-entry.ts +68 -0
- package/src/cli/inference.ts +214 -0
- package/src/cli/job-entry.ts +97 -0
- package/src/cli/library-host.ts +117 -0
- package/src/cli/main.ts +57 -0
- package/src/cli/memory-engine.ts +163 -0
- package/src/cli/memory.ts +36 -0
- package/src/cli/mlx-lm-aliases.ts +309 -0
- package/src/cli/model-choice.ts +33 -0
- package/src/cli/model-selection.ts +84 -0
- package/src/cli/module-host.ts +11 -0
- package/src/cli/module-verbs.ts +50 -0
- package/src/cli/numerical-policy.ts +12 -0
- package/src/cli/perplexity.ts +89 -0
- package/src/cli/serve-host.ts +423 -0
- package/src/cli/serve-isolated.ts +217 -0
- package/src/cli/serve-options.ts +106 -0
- package/src/cli/serve-state.ts +205 -0
- package/src/cli/serve.ts +500 -0
- package/src/cli/served-model-host.ts +102 -0
- package/src/cli/server-entry.ts +148 -0
- package/src/cli/serving-unit.ts +177 -0
- package/src/{tui.ts → cli/terminal.ts} +13 -8
- package/src/cli/worker-adapters.ts +34 -0
- package/src/cli/worker-entry.ts +272 -0
- package/src/cli/worker-unit.ts +179 -0
- package/src/engine/adapter-operation.ts +26 -0
- package/src/engine/cache-services.ts +114 -0
- package/src/engine/completion.ts +39 -0
- package/src/engine/generation-gateway.ts +358 -0
- package/src/engine/index.ts +59 -0
- package/src/engine/kv-budget.ts +120 -0
- package/src/engine/media-preparation.ts +232 -0
- package/src/engine/model-binding.ts +24 -0
- package/src/engine/model-host.ts +513 -0
- package/src/engine/model-serving.ts +89 -0
- package/src/{serve → engine}/preparation.ts +7 -5
- package/src/{serve → engine}/session-completion-engine.ts +6 -6
- package/src/engine/telemetry.ts +109 -0
- package/src/hub/downloads.ts +116 -0
- package/src/jobs/db.ts +16 -7
- package/src/jobs/events.ts +29 -0
- package/src/jobs/executable.ts +5 -0
- package/src/jobs/host.ts +51 -0
- package/src/jobs/in-process.ts +55 -0
- package/src/{contracts/jobs.ts → jobs/protocol.ts} +7 -9
- package/src/jobs/runner.ts +150 -231
- package/src/jobs/service.ts +75 -0
- package/src/jobs/sse.ts +59 -23
- package/src/jobs/worker-memory.ts +46 -0
- package/src/jobs/worker-process.ts +163 -0
- package/src/jobs/worker-supervisor.ts +258 -0
- package/src/modules.ts +58 -0
- package/src/publishing/catalog-hub.ts +18 -0
- package/src/publishing/credentials.ts +31 -0
- package/src/publishing/upload.ts +37 -0
- package/src/residency/leased-adapters.ts +21 -0
- package/src/residency/local-records.ts +16 -0
- package/src/residency/model-residency.ts +405 -0
- package/src/residency/record-index.ts +28 -0
- package/src/residency/resident-estimate.ts +50 -0
- package/src/{anthropic.ts → server/anthropic.ts} +3 -81
- package/src/server/cache-routes.ts +36 -0
- package/src/{serve → server}/chat-request.ts +10 -10
- package/src/{serve → server}/chat-stage.ts +14 -14
- package/src/{client.ts → server/client.ts} +28 -2
- package/src/{serve → server}/completion-executor.ts +33 -41
- package/src/server/decision-body.ts +55 -0
- package/src/server/decision-routes.ts +134 -0
- package/src/server/decision-wire.ts +228 -0
- package/src/{serve → server}/discovery-routes.ts +25 -82
- package/src/{serve → server}/http.ts +25 -13
- package/src/{serve → server}/inference-request.ts +5 -5
- package/src/server/job-routes.ts +20 -0
- package/src/server/media-prompt.ts +63 -0
- package/src/server/memory-completion-client.ts +46 -0
- package/src/server/model-routes.ts +192 -0
- package/src/{serve → server}/openai-wire.ts +3 -2
- package/src/{serve → server}/pipeline.ts +1 -1
- package/src/server/prompt-builder.ts +13 -0
- package/src/server/prompt-contracts.ts +17 -0
- package/src/server/proxy-routes.ts +187 -0
- package/src/server/publishing-routes.ts +41 -0
- package/src/{serve → server}/request-plan.ts +6 -8
- package/src/{serve → server}/request-prep.ts +47 -50
- package/src/{serve → server}/responses-client.ts +30 -13
- package/src/{responses.ts → server/responses.ts} +12 -93
- package/src/server/routes.ts +205 -0
- package/src/server/start.ts +72 -0
- package/src/server/status-routes.ts +108 -0
- package/src/{serve → server}/text-completion-stage.ts +25 -4
- package/src/server/text-prompt.ts +12 -0
- package/src/{serve → server}/token-streams.ts +36 -38
- package/src/server/worker-routes.ts +311 -0
- package/src/storage/paths.ts +61 -0
- package/src/web/assets.ts +37 -0
- package/src/web/{src → browser}/api.ts +5 -23
- package/src/web/browser/chat-host.ts +17 -0
- package/src/web/browser/installed-panels.ts +6 -0
- package/src/web/browser/main.ts +55 -0
- package/src/web/browser/markdown.ts +279 -0
- package/src/web/browser/memory-panel.ts +9 -0
- package/src/web/browser/palette.ts +64 -0
- package/src/web/browser/protocol.ts +13 -0
- package/src/web/browser/shell.ts +467 -0
- package/src/web/{src → browser}/status.ts +32 -36
- package/src/web/build.ts +51 -0
- package/src/web/public/app.html +1054 -0
- package/src/web/{sw.js → public/sw.js} +4 -3
- package/src/web/{vendor → public/vendor}/hljs-theme.css +2 -2
- package/src/web/{vendor → public/vendor}/hljs.js +2 -2
- package/THIRD_PARTY_LICENSES.md +0 -51
- package/docs/README.md +0 -107
- package/docs/archive/hlg-sampling.md +0 -350
- package/docs/archive/investigations/audio-input-plan.md +0 -356
- package/docs/archive/investigations/benchmark-ledger-through-v0.4.0.md +0 -3617
- package/docs/archive/investigations/chunk-finetune-experiment.md +0 -84
- package/docs/archive/investigations/colibri-glm52-port.md +0 -1486
- package/docs/archive/investigations/colibri-oracle-pin.md +0 -1284
- package/docs/archive/investigations/curve-bisector-routing.md +0 -123
- package/docs/archive/investigations/decode-roofline-lookagain.md +0 -348
- package/docs/archive/investigations/diffusion-gemma-port.md +0 -602
- package/docs/archive/investigations/dspark-handoff.md +0 -224
- package/docs/archive/investigations/expert-offload-single-user-moe.md +0 -292
- package/docs/archive/investigations/faithful-l1-consolidation.md +0 -310
- package/docs/archive/investigations/hlg-sampling-investigation.md +0 -970
- package/docs/archive/investigations/indexshare-performance-spike.md +0 -357
- package/docs/archive/investigations/kernel-perf-review-2026-07.md +0 -171
- package/docs/archive/investigations/lab-build-journal.md +0 -157
- package/docs/archive/investigations/minicpm5-decode-megakernel.md +0 -489
- package/docs/archive/investigations/openjarvis-fit.md +0 -198
- package/docs/archive/investigations/openwiki-evaluation.md +0 -249
- package/docs/archive/investigations/optimization_plan.md +0 -346
- package/docs/archive/investigations/orpo-base-uf-experiment-and-directions.md +0 -120
- package/docs/archive/investigations/orpo-flash-cce-pin-leak.md +0 -167
- package/docs/archive/investigations/orpo-uf-testing-handoff.md +0 -126
- package/docs/archive/investigations/pareto-specialized-runtime-findings.md +0 -92
- package/docs/archive/investigations/pi-builtin-investigation.md +0 -308
- package/docs/archive/investigations/pre-colibri-stabilization.md +0 -329
- package/docs/archive/investigations/qwen38-27b-campaign-log.md +0 -1092
- package/docs/archive/investigations/segmented-backward-handoff.md +0 -148
- package/docs/archive/investigations/starter-model-port-handoff.md +0 -342
- package/docs/archive/investigations/steel-flash-cce-handoff.md +0 -489
- package/docs/archive/investigations/the-dreaming-handoff.md +0 -141
- package/docs/archive/investigations/trainer-validation-experiment.md +0 -173
- package/docs/archive/planning/chat-ui-vision-memory-first.md +0 -199
- package/docs/archive/planning/chat-ui-vision.md +0 -50
- package/docs/archive/planning/chunk-task-roadmap.md +0 -77
- package/docs/archive/planning/curve-sampler-research-plan.md +0 -331
- package/docs/archive/planning/journal.md +0 -316
- package/docs/archive/planning/memory-docs-and-dag-plan.md +0 -194
- package/docs/archive/planning/pi-integration-review.md +0 -178
- package/docs/archive/planning/release-notes-v0.0.10.md +0 -57
- package/docs/archive/planning/release-notes-v0.0.11.md +0 -239
- package/docs/archive/planning/release-notes-v0.0.12.md +0 -80
- package/docs/archive/planning/release-notes-v0.0.13.md +0 -77
- package/docs/archive/planning/release-notes-v0.0.9.md +0 -122
- package/docs/archive/planning/release-notes-v0.2.0.md +0 -49
- package/docs/archive/planning/release-notes-v0.3.0.md +0 -35
- package/docs/archive/planning/release-notes-v0.4.0.md +0 -45
- package/docs/archive/planning/release-notes-v0.5.0.md +0 -37
- package/docs/archive/planning/web-ui-pass-plan.md +0 -213
- package/docs/archive/planning/website-readme-pass-plan.md +0 -224
- package/docs/design/batching.md +0 -2054
- package/docs/design/decode-speed-program.md +0 -2404
- package/docs/design/dreaming-nightly-pipeline.md +0 -684
- package/docs/design/generic-model-support.md +0 -821
- package/docs/design/kv-cache.md +0 -993
- package/docs/design/orpo-training.md +0 -680
- package/docs/design/speculative-decoding.md +0 -2129
- package/docs/design/turboquant.md +0 -2225
- package/docs/design/unified-engine-frontier-plan.md +0 -1987
- package/docs/design/web-chat-redesign.md +0 -695
- package/docs/planning/PRODUCT_ROADMAP.md +0 -593
- package/docs/planning/ResearchTopics.md +0 -119
- package/docs/planning/public-readiness-handoff.md +0 -244
- package/docs/planning/public-readiness.md +0 -149
- package/docs/planning/video-series.md +0 -421
- package/docs/reference/benchmarks.md +0 -3587
- package/docs/reference/cli.md +0 -646
- package/docs/reference/distribution.md +0 -380
- package/docs/reference/environment.md +0 -494
- package/docs/reference/glossary.md +0 -39
- package/docs/reference/library-api.md +0 -688
- package/docs/reference/memory.md +0 -260
- package/docs/reference/models.md +0 -346
- package/docs/reference/server-api.md +0 -1945
- package/docs/reference/server-config.md +0 -973
- package/docs/reference/training.md +0 -462
- package/docs/reference/troubleshooting.md +0 -158
- package/src/admission-plan.ts +0 -18
- package/src/audio/audiotoolbox.ts +0 -112
- package/src/audio/conformer.ts +0 -655
- package/src/audio/decode.ts +0 -133
- package/src/audio/features.ts +0 -202
- package/src/audio/mic-capture.ts +0 -98
- package/src/audio/silero-vad.ts +0 -379
- package/src/audio/transcode.ts +0 -67
- package/src/audio/whisper-decode.ts +0 -774
- package/src/audio/whisper-fast.ts +0 -732
- package/src/audio/whisper-format.ts +0 -73
- package/src/audio/whisper-mel.ts +0 -185
- package/src/audio/whisper-timing.ts +0 -274
- package/src/audio/whisper-tokenizer.ts +0 -239
- package/src/audio/whisper-transcribe.ts +0 -410
- package/src/backends/mlx/assistant-target.ts +0 -46
- package/src/backends/mlx/autoregressive-draft-rows.ts +0 -120
- package/src/backends/mlx/autoregressive.ts +0 -129
- package/src/backends/mlx/batch-group.ts +0 -1874
- package/src/backends/mlx/cache-attachment.ts +0 -67
- package/src/backends/mlx/cache-layout.ts +0 -31
- package/src/backends/mlx/checkpoint-state.ts +0 -53
- package/src/backends/mlx/continuation-persistence.ts +0 -107
- package/src/backends/mlx/continuation-request.ts +0 -43
- package/src/backends/mlx/continuation.ts +0 -77
- package/src/backends/mlx/diffusion.ts +0 -39
- package/src/backends/mlx/draft-target.ts +0 -32
- package/src/backends/mlx/encoder-cache.ts +0 -25
- package/src/backends/mlx/evaluation.ts +0 -105
- package/src/backends/mlx/fill-append.ts +0 -58
- package/src/backends/mlx/fill-group.ts +0 -497
- package/src/backends/mlx/full-kv-row-append.ts +0 -32
- package/src/backends/mlx/full-kv-row-donor.ts +0 -54
- package/src/backends/mlx/full-transitioning-kv-rows.ts +0 -65
- package/src/backends/mlx/gateway-binding.ts +0 -174
- package/src/backends/mlx/graph.ts +0 -38
- package/src/backends/mlx/kv-codec.ts +0 -9
- package/src/backends/mlx/kv-maintenance.ts +0 -161
- package/src/backends/mlx/kv-tensor-rows.ts +0 -212
- package/src/backends/mlx/memory-client.ts +0 -189
- package/src/backends/mlx/methods.ts +0 -90
- package/src/backends/mlx/mixed-iteration.ts +0 -62
- package/src/backends/mlx/model-prompt.ts +0 -178
- package/src/backends/mlx/model-serving.ts +0 -80
- package/src/backends/mlx/paged-cache-codec.ts +0 -27
- package/src/backends/mlx/prefill-cohort.ts +0 -100
- package/src/backends/mlx/prefill-policy.ts +0 -26
- package/src/backends/mlx/prefill-rows.ts +0 -244
- package/src/backends/mlx/prefill.ts +0 -33
- package/src/backends/mlx/projected-context-rows.ts +0 -72
- package/src/backends/mlx/prompt-input.ts +0 -29
- package/src/backends/mlx/qwen-prompt-input.ts +0 -40
- package/src/backends/mlx/request-policy.ts +0 -20
- package/src/backends/mlx/request-state-policy.ts +0 -54
- package/src/backends/mlx/rollback.ts +0 -92
- package/src/backends/mlx/row-sampling.ts +0 -49
- package/src/backends/mlx/serial-executor.ts +0 -251
- package/src/backends/mlx/speculative-group.ts +0 -438
- package/src/backends/mlx/speculative-round.ts +0 -161
- package/src/backends/mlx/speculative.ts +0 -131
- package/src/backends/mlx/state-rows.ts +0 -104
- package/src/backends/mlx/state-views.ts +0 -46
- package/src/backends/mlx/target-layout-capability.ts +0 -35
- package/src/backends/mlx/text-engine.ts +0 -73
- package/src/backends/mlx/transitioning-kv-rows.ts +0 -173
- package/src/chat-template.ts +0 -382
- package/src/cli.ts +0 -3490
- package/src/config.ts +0 -399
- package/src/contracts/completion.ts +0 -16
- package/src/contracts/execution.ts +0 -70
- package/src/contracts/generation.ts +0 -103
- package/src/contracts/host.ts +0 -7
- package/src/contracts/object-cache.ts +0 -8
- package/src/contracts/pi-provider.ts +0 -30
- package/src/contracts/pi.ts +0 -261
- package/src/contracts/prefix-cache.ts +0 -33
- package/src/contracts/resources.ts +0 -25
- package/src/contracts/sampling.ts +0 -23
- package/src/contracts/scheduling.ts +0 -54
- package/src/contracts/tsconfig.json +0 -14
- package/src/dataset/generators.ts +0 -1087
- package/src/dataset/index.ts +0 -12
- package/src/dataset/job.ts +0 -81
- package/src/dataset/llm.ts +0 -71
- package/src/dataset/registry.ts +0 -528
- package/src/diffusion/diffusion-generate.ts +0 -507
- package/src/download.ts +0 -505
- package/src/embed.ts +0 -77
- package/src/engine/admission.ts +0 -59
- package/src/engine/cancellation.ts +0 -38
- package/src/engine/engine.ts +0 -56
- package/src/engine/execution-coordinator.ts +0 -70
- package/src/engine/execution-plan.ts +0 -64
- package/src/engine/execution-tasks.ts +0 -44
- package/src/engine/namespaced-cache.ts +0 -13
- package/src/engine/resources.ts +0 -53
- package/src/engine/scheduler.ts +0 -92
- package/src/engine/session.ts +0 -247
- package/src/eval/README.md +0 -130
- package/src/eval/ifeval.ts +0 -81
- package/src/eval/kl-prompts.ts +0 -29
- package/src/eval/kl.ts +0 -590
- package/src/eval/perplexity.ts +0 -164
- package/src/eval/quality-db.ts +0 -92
- package/src/eval/reference-logits.ts +0 -207
- package/src/eval/runner.ts +0 -124
- package/src/eval/score.ts +0 -49
- package/src/eval/tasks/bfcl.ts +0 -535
- package/src/eval/tasks/gsm8k.ts +0 -98
- package/src/eval/tasks/hashhop.ts +0 -139
- package/src/eval/tasks/humaneval.ts +0 -275
- package/src/eval/tasks/ifeval.ts +0 -525
- package/src/eval/tasks/mmlu.ts +0 -157
- package/src/eval/ultrafeedback.ts +0 -60
- package/src/evaldb.ts +0 -199
- package/src/expert-io.ts +0 -393
- package/src/expert-offload-build.ts +0 -77
- package/src/expert-offload.ts +0 -76
- package/src/expert-residency.ts +0 -1134
- package/src/expert-usage.ts +0 -415
- package/src/fill/echo-index.ts +0 -188
- package/src/fill/fill-session.ts +0 -463
- package/src/fill/proposal.ts +0 -68
- package/src/fill/schema-rows.ts +0 -506
- package/src/fill/tool-boundary.ts +0 -103
- package/src/fit.ts +0 -249
- package/src/generate.ts +0 -1352
- package/src/grammar.ts +0 -604
- package/src/harness-pi.ts +0 -184
- package/src/hf-push.ts +0 -628
- package/src/hub-rest.ts +0 -242
- package/src/index.ts +0 -63
- package/src/inference/denoising.ts +0 -17
- package/src/inference/draft-acceptance.ts +0 -41
- package/src/inference/draft-output.ts +0 -49
- package/src/inference/graph.ts +0 -33
- package/src/inference/prefill.ts +0 -45
- package/src/inference/rollback.ts +0 -7
- package/src/jobs/index.ts +0 -31
- package/src/jobs/job-entry.ts +0 -107
- package/src/jobs/task-client.ts +0 -19
- package/src/jobs/types.ts +0 -2
- package/src/kv-scheme.ts +0 -279
- package/src/kv-store.ts +0 -1135
- package/src/lab/README.md +0 -22
- package/src/lab/curve/curve-designer.html +0 -303
- package/src/lab/curve/curve-sampler.ts +0 -122
- package/src/lab/expert-trace/expert-trace.ts +0 -69
- package/src/lab/paged-kv/paged-attention.ts +0 -117
- package/src/lab/paged-kv/paged-kv-rows.ts +0 -111
- package/src/lab/paged-kv/paged-kv.ts +0 -336
- package/src/library.ts +0 -40
- package/src/lora.ts +0 -408
- package/src/md-modules.d.ts +0 -54
- package/src/media-fetch.ts +0 -442
- package/src/memory/article.ts +0 -558
- package/src/memory/chunk-validation.ts +0 -150
- package/src/memory/chunk.ts +0 -318
- package/src/memory/cluster.ts +0 -351
- package/src/memory/coverage.ts +0 -134
- package/src/memory/crosslink.ts +0 -0
- package/src/memory/db.ts +0 -253
- package/src/memory/entity.ts +0 -203
- package/src/memory/gate.ts +0 -187
- package/src/memory/history.ts +0 -152
- package/src/memory/ingest.ts +0 -335
- package/src/memory/model.ts +0 -183
- package/src/memory/normalize.ts +0 -671
- package/src/memory/parse.ts +0 -22
- package/src/memory/pipeline.ts +0 -275
- package/src/memory/prompts.ts +0 -65
- package/src/memory/query.ts +0 -334
- package/src/memory/reconcile.ts +0 -734
- package/src/memory/redirect.ts +0 -108
- package/src/memory/reindex.ts +0 -325
- package/src/memory/resolve.ts +0 -307
- package/src/memory/rest.ts +0 -245
- package/src/memory/route.ts +0 -272
- package/src/memory/schedule.ts +0 -165
- package/src/memory/stages.ts +0 -777
- package/src/memory/synthesize.ts +0 -1638
- package/src/memory/tools.ts +0 -749
- package/src/memory/vault.ts +0 -1194
- package/src/memory/wikify.ts +0 -1064
- package/src/mlx/array.ts +0 -425
- package/src/mlx/autograd.ts +0 -304
- package/src/mlx/checkpoint.ts +0 -149
- package/src/mlx/compile.ts +0 -168
- package/src/mlx/custom-vjp.ts +0 -188
- package/src/mlx/ffi.ts +0 -531
- package/src/mlx/materialize.ts +0 -23
- package/src/mlx/metal-kernel.ts +0 -159
- package/src/mlx/normalized-argmax.ts +0 -116
- package/src/mlx/ops.ts +0 -1108
- package/src/mlx/quantized-rows.ts +0 -36
- package/src/mlx/token-bitmask.ts +0 -52
- package/src/mlx/turboquant-kv-decode.ts +0 -82
- package/src/mlx/turboquant-kv-inverse.ts +0 -122
- package/src/mlx/turboquant-ops.ts +0 -861
- package/src/mlx/turboquant-tables.ts +0 -237
- package/src/mmap.ts +0 -90
- package/src/model/artifact-identity.ts +0 -73
- package/src/model/batched-kv.ts +0 -235
- package/src/model/batched-mask.ts +0 -279
- package/src/model/batched-quant.ts +0 -212
- package/src/model/batched-quantized-kv.ts +0 -144
- package/src/model/batched-rotating-quant.ts +0 -353
- package/src/model/batched-rotating-state.ts +0 -149
- package/src/model/batched-rotating.ts +0 -338
- package/src/model/batched-row-storage.ts +0 -179
- package/src/model/batched-ssm.ts +0 -52
- package/src/model/batched-turboquant-kv.ts +0 -94
- package/src/model/compiled-decode.ts +0 -712
- package/src/model/decoded-kv-donor.ts +0 -12
- package/src/model/delayed-quantized-kv.ts +0 -72
- package/src/model/delayed-rotating-quantized-kv.ts +0 -93
- package/src/model/delayed-turboquant-kv.ts +0 -57
- package/src/model/diffusion-gemma.ts +0 -1026
- package/src/model/factory.ts +0 -215
- package/src/model/fingerprint.ts +0 -45
- package/src/model/flash-attention.ts +0 -694
- package/src/model/full-prefill-padding.ts +0 -56
- package/src/model/full-prefill-row.ts +0 -105
- package/src/model/gemma4-base.ts +0 -2370
- package/src/model/gemma4.ts +0 -1395
- package/src/model/generated/gemma4-12b.ts +0 -429
- package/src/model/generated/gemma4-26b.ts +0 -448
- package/src/model/generated/gemma4-e4b.ts +0 -717
- package/src/model/generated/index.ts +0 -18
- package/src/model/glm52-atlas.ts +0 -511
- package/src/model/glm52-cache.ts +0 -792
- package/src/model/glm52-config.ts +0 -262
- package/src/model/glm52-container.ts +0 -305
- package/src/model/glm52-coupling.ts +0 -388
- package/src/model/glm52-dsa.ts +0 -606
- package/src/model/glm52-expert-layout.ts +0 -253
- package/src/model/glm52-layout.ts +0 -152
- package/src/model/glm52-memory.ts +0 -500
- package/src/model/glm52-mla.ts +0 -792
- package/src/model/glm52-moe.ts +0 -223
- package/src/model/glm52-pilot.ts +0 -537
- package/src/model/glm52-reference.ts +0 -445
- package/src/model/glm52-residency.ts +0 -557
- package/src/model/glm52-resident-weights.ts +0 -135
- package/src/model/glm52-streamed-experts.ts +0 -453
- package/src/model/glm52-streamed-metal.ts +0 -610
- package/src/model/glm52-weights.ts +0 -503
- package/src/model/glm52.ts +0 -1132
- package/src/model/implementation.ts +0 -69
- package/src/model/kv-attention-view.ts +0 -70
- package/src/model/minicpm5.ts +0 -376
- package/src/model/profile.ts +0 -468
- package/src/model/quantized-append-attention.ts +0 -31
- package/src/model/qwen-append.ts +0 -12
- package/src/model/qwen-conv.ts +0 -52
- package/src/model/qwen3-delta-state.ts +0 -74
- package/src/model/qwen3-delta.ts +0 -451
- package/src/model/qwen3-moe.ts +0 -404
- package/src/model/qwen3-mrope.ts +0 -250
- package/src/model/qwen3.ts +0 -277
- package/src/model/qwen3_5-checkpoint.ts +0 -164
- package/src/model/qwen3_5.ts +0 -881
- package/src/model/rotating-kv-donor.ts +0 -35
- package/src/model/rotating-kv-layout.ts +0 -262
- package/src/model/rotating-row-transaction.ts +0 -35
- package/src/model/speculative-rotating-kv.ts +0 -101
- package/src/model/ssm-prefill-padding.ts +0 -39
- package/src/model/support.ts +0 -110
- package/src/model/token-groups.ts +0 -66
- package/src/model/trellis-balanced-scatter.ts +0 -51
- package/src/model/trellis-linear.ts +0 -672
- package/src/model/trellis-shared-m.ts +0 -151
- package/src/model/trellis-shared-scatter.ts +0 -144
- package/src/model/trellis-splitk-prefill.ts +0 -158
- package/src/model/trellis-tiled-prefill.ts +0 -136
- package/src/model/trellis-vector-expand.ts +0 -62
- package/src/model/trellis-wide-prefill.ts +0 -94
- package/src/model/turboquant-codec.ts +0 -89
- package/src/model/universal/archs.ts +0 -332
- package/src/model/universal/dense.ts +0 -612
- package/src/model/universal/modules.ts +0 -170
- package/src/model/universal/rope.ts +0 -381
- package/src/model/whisper.ts +0 -435
- package/src/native/expert_io.c +0 -523
- package/src/native/frame_extract.swift +0 -140
- package/src/native/mic_capture.swift +0 -92
- package/src/native-pack.ts +0 -163
- package/src/pi-provider.ts +0 -53
- package/src/pi-session.ts +0 -81
- package/src/pi-terminal.ts +0 -274
- package/src/pi-web.ts +0 -1940
- package/src/preflight.ts +0 -158
- package/src/prompt-cache.ts +0 -682
- package/src/quantize/allocator.ts +0 -627
- package/src/quantize/atomic-output.ts +0 -38
- package/src/quantize/calibration.ts +0 -181
- package/src/quantize/config-writer.ts +0 -145
- package/src/quantize/index.ts +0 -84
- package/src/quantize/job.ts +0 -162
- package/src/quantize/quantizer.ts +0 -666
- package/src/quantize/rotate.ts +0 -603
- package/src/quantize/safetensors-writer.ts +0 -249
- package/src/quantize/sensitivity.ts +0 -216
- package/src/quantize/trellis.ts +0 -586
- package/src/quantize/weight-transform.ts +0 -292
- package/src/registry.ts +0 -602
- package/src/runtime-config.ts +0 -77
- package/src/safetensors.ts +0 -165
- package/src/sampler.ts +0 -1012
- package/src/serve/admin-routes.ts +0 -184
- package/src/serve/assets/curve-terrain.html +0 -105
- package/src/serve/assets/training-inference-map.html +0 -575
- package/src/serve/audio-routes.ts +0 -323
- package/src/serve/aux-routes.ts +0 -132
- package/src/serve/batch-scheduler.ts +0 -8
- package/src/serve/checkpoint-identity.ts +0 -64
- package/src/serve/generation-gateway.ts +0 -537
- package/src/serve/isolate.ts +0 -609
- package/src/serve/kv-budget.ts +0 -14
- package/src/serve/lab-routes.ts +0 -341
- package/src/serve/lane-registry.ts +0 -45
- package/src/serve/model-admin-routes.ts +0 -155
- package/src/serve/model-binding.ts +0 -49
- package/src/serve/model-host.ts +0 -511
- package/src/serve/parent-application.ts +0 -56
- package/src/serve/prompt-response-trace.ts +0 -161
- package/src/serve/response-lease.ts +0 -24
- package/src/serve/session-search.ts +0 -326
- package/src/serve/static-routes.ts +0 -101
- package/src/serve/transcription-server.ts +0 -64
- package/src/serve/transcription-service.ts +0 -502
- package/src/server.ts +0 -1282
- package/src/spec/assistant-rows.ts +0 -93
- package/src/spec/assistant-source.ts +0 -126
- package/src/spec/cache-identity.ts +0 -22
- package/src/spec/deepspec-rows.ts +0 -29
- package/src/spec/deepspec-source.ts +0 -145
- package/src/spec/dflash-source.ts +0 -149
- package/src/spec/drafter.ts +0 -304
- package/src/spec/dspark/ab-stats.ts +0 -136
- package/src/spec/dspark/calibration.ts +0 -112
- package/src/spec/dspark/data-dflash.ts +0 -156
- package/src/spec/dspark/data.ts +0 -201
- package/src/spec/dspark/deepspec-module.ts +0 -776
- package/src/spec/dspark/generate-dflash.ts +0 -245
- package/src/spec/dspark/generate.ts +0 -290
- package/src/spec/dspark/loader.ts +0 -23
- package/src/spec/dspark/loss.ts +0 -213
- package/src/spec/dspark/module-dflash.ts +0 -560
- package/src/spec/dspark/module.ts +0 -540
- package/src/spec/dspark/quantize-drafter.ts +0 -91
- package/src/spec/dspark/sample.ts +0 -112
- package/src/spec/generate.ts +0 -276
- package/src/spec/glm52-mtp-graph.ts +0 -29
- package/src/spec/glm52-mtp-rows.ts +0 -132
- package/src/spec/glm52-mtp-source.ts +0 -318
- package/src/spec/glm52-mtp-state.ts +0 -33
- package/src/spec/ngram-source.ts +0 -226
- package/src/spec/projected-draft-rows.ts +0 -73
- package/src/spec/qwen-mtp-module.ts +0 -171
- package/src/spec/qwen-mtp-rows.ts +0 -230
- package/src/spec/qwen-mtp-source.ts +0 -386
- package/src/spec/qwen-mtp-state.ts +0 -30
- package/src/spec/serve-loop.ts +0 -587
- package/src/spec/source.ts +0 -228
- package/src/spec/two-model-rows.ts +0 -62
- package/src/spec/two-model.ts +0 -113
- package/src/ssd-cache.ts +0 -431
- package/src/ssd-durability.ts +0 -209
- package/src/storage/host-buffer.ts +0 -24
- package/src/storage/kv-writer.ts +0 -83
- package/src/storage/kv-writer.worker.js +0 -264
- package/src/storage/retention-policy.ts +0 -37
- package/src/tiered-prompt-cache.ts +0 -45
- package/src/tokenizer.ts +0 -335
- package/src/tool-approvals.ts +0 -131
- package/src/tool-call.ts +0 -695
- package/src/train/dataset.ts +0 -484
- package/src/train/diffusion-lora.ts +0 -216
- package/src/train/export.ts +0 -32
- package/src/train/flash-cce.ts +0 -1234
- package/src/train/forward.ts +0 -266
- package/src/train/fuse.ts +0 -243
- package/src/train/index.ts +0 -46
- package/src/train/job.ts +0 -205
- package/src/train/lora-params.ts +0 -254
- package/src/train/loss.ts +0 -1591
- package/src/train/merge.ts +0 -195
- package/src/train/optimizer.ts +0 -183
- package/src/train/prefix-shared.ts +0 -490
- package/src/train/rank.ts +0 -137
- package/src/train/segmented.ts +0 -1828
- package/src/train/steel-qmm-header.ts +0 -1461
- package/src/train/trainer.ts +0 -1221
- package/src/train/watch.ts +0 -542
- package/src/vision/diffusion-vision.ts +0 -620
- package/src/vision/embedder.ts +0 -176
- package/src/vision/preprocess.ts +0 -247
- package/src/vision/prompt.ts +0 -409
- package/src/vision/qwen3vl-preprocess.ts +0 -409
- package/src/vision/qwen3vl-prompt.ts +0 -233
- package/src/vision/qwen3vl-tower.ts +0 -566
- package/src/vision/siglip.ts +0 -607
- package/src/vision/video-frames.ts +0 -148
- package/src/web/app.html +0 -2155
- package/src/web/app.js +0 -5863
- package/src/web/skills/memory/SKILL.md +0 -120
- package/src/web/skills/web-research/SKILL.md +0 -46
- package/src/web/skills.ts +0 -97
- package/src/web/src/adapters-panel.ts +0 -288
- package/src/web/src/assistant.ts +0 -451
- package/src/web/src/chat.ts +0 -1060
- package/src/web/src/composer.ts +0 -962
- package/src/web/src/dataset.ts +0 -132
- package/src/web/src/finetune.ts +0 -267
- package/src/web/src/hub.ts +0 -428
- package/src/web/src/main.ts +0 -98
- package/src/web/src/markdown.ts +0 -0
- package/src/web/src/memory-panel.ts +0 -572
- package/src/web/src/model-picker.ts +0 -159
- package/src/web/src/palette.ts +0 -346
- package/src/web/src/protocol.ts +0 -26
- package/src/web/src/quantize.ts +0 -167
- package/src/web/src/rag.ts +0 -274
- package/src/web/src/sessions.ts +0 -469
- package/src/web/src/shell.ts +0 -804
- package/src/web/src/ui-catalog.ts +0 -78
- package/src/web/src/voice.ts +0 -141
- package/src/web/vendor/README.md +0 -61
- package/src/web-assets.ts +0 -47
- package/src/web-tools.ts +0 -478
- package/src/weights.ts +0 -255
- /package/src/{serve → server}/completion-sink.ts +0 -0
- /package/src/{serve → server}/generated-token-history.ts +0 -0
- /package/src/web/{icon.svg → public/icon.svg} +0 -0
- /package/src/web/{manifest.webmanifest → public/manifest.webmanifest} +0 -0
- /package/src/web/{vendor → public/vendor}/hljs-LICENSE +0 -0
package/src/download.ts
DELETED
|
@@ -1,505 +0,0 @@
|
|
|
1
|
-
// Resumable HF downloads with checksum verification (Phase 5).
|
|
2
|
-
//
|
|
3
|
-
// The Python downloader failure modes this replaces (PLAN "Context /
|
|
4
|
-
// lore"): Xet stalls (we speak plain HTTPS resolve/CDN), no resume on
|
|
5
|
-
// flaky links (we Range-resume partial blobs), and silent corruption
|
|
6
|
-
// (every blob is verified — sha256 for LFS files against the API's
|
|
7
|
-
// normalized LFS digest, git-blob sha1 for small files against blobId).
|
|
8
|
-
//
|
|
9
|
-
// Writes the exact huggingface_hub cache layout so the registry and
|
|
10
|
-
// loaders find the result with zero changes:
|
|
11
|
-
// <hub>/models--{org}--{name}/blobs/<sha> (verified bytes)
|
|
12
|
-
// <hub>/models--{org}--{name}/snapshots/<commit>/<file> → ../../blobs/<sha>
|
|
13
|
-
// <hub>/models--{org}--{name}/refs/<revision> (commit sha)
|
|
14
|
-
//
|
|
15
|
-
// Files download sequentially (decode is bandwidth-bound, the network
|
|
16
|
-
// rarely is; resumability matters more than parallelism here).
|
|
17
|
-
|
|
18
|
-
import {
|
|
19
|
-
mkdirSync, existsSync, statSync, createWriteStream, renameSync, rmSync,
|
|
20
|
-
symlinkSync, readFileSync, openSync, closeSync, readSync, writeSync,
|
|
21
|
-
statfsSync,
|
|
22
|
-
} from "node:fs";
|
|
23
|
-
import { dirname, join } from "node:path";
|
|
24
|
-
import { DEFAULT_HUB } from "./registry";
|
|
25
|
-
|
|
26
|
-
const ENDPOINT = "https://huggingface.co";
|
|
27
|
-
|
|
28
|
-
export interface RepoFile {
|
|
29
|
-
rfilename: string;
|
|
30
|
-
size: number;
|
|
31
|
-
/** git blob sha1 (non-LFS identity, and the blob filename for non-LFS). */
|
|
32
|
-
blobId: string;
|
|
33
|
-
/** LFS sha256 + size; present for large files. sha256 is the blob filename. */
|
|
34
|
-
lfs?: { sha256: string; size: number };
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export interface RepoListing {
|
|
38
|
-
/** Commit sha the revision resolves to (snapshot dir name). */
|
|
39
|
-
sha: string;
|
|
40
|
-
files: RepoFile[];
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export interface DownloadOptions {
|
|
44
|
-
revision?: string;
|
|
45
|
-
cacheDir?: string;
|
|
46
|
-
endpoint?: string;
|
|
47
|
-
token?: string | null;
|
|
48
|
-
onProgress?: (file: string, received: number, total: number) => void;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export interface DownloadSpacePlan {
|
|
52
|
-
/** Bytes still absent after crediting verified blobs and resumable prefixes. */
|
|
53
|
-
remainingBytes: number;
|
|
54
|
-
/** Files that still need at least one byte. */
|
|
55
|
-
remainingFiles: number;
|
|
56
|
-
/** Files already complete in the shared HF blob store. */
|
|
57
|
-
completeFiles: number;
|
|
58
|
-
/** Existing `.incomplete` bytes that will be rehashed and resumed. */
|
|
59
|
-
resumableBytes: number;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/** One GiB for filesystem metadata, the final refs write, and normal host
|
|
63
|
-
* operation. The artifact bytes themselves are exact; this is deliberately a
|
|
64
|
-
* small fixed guard rather than a percentage that would add tens of GiB to a
|
|
65
|
-
* Colibri download. */
|
|
66
|
-
export const DOWNLOAD_DISK_RESERVE_BYTES = 2 ** 30;
|
|
67
|
-
|
|
68
|
-
/** Compute the exact remaining payload without opening or hashing any blob.
|
|
69
|
-
* A valid-size final blob is already complete; an in-range `.incomplete`
|
|
70
|
-
* prefix is credited because downloadOneLocked resumes it in place. */
|
|
71
|
-
export function planDownloadSpace(
|
|
72
|
-
files: readonly RepoFile[],
|
|
73
|
-
blobsDir: string,
|
|
74
|
-
): DownloadSpacePlan {
|
|
75
|
-
let remainingBytes = 0;
|
|
76
|
-
let remainingFiles = 0;
|
|
77
|
-
let completeFiles = 0;
|
|
78
|
-
let resumableBytes = 0;
|
|
79
|
-
for (const file of files) {
|
|
80
|
-
const blobId = file.lfs?.sha256 ?? file.blobId;
|
|
81
|
-
if (!blobId) throw new Error(`no blob id for ${file.rfilename}`);
|
|
82
|
-
const blobPath = join(blobsDir, blobId);
|
|
83
|
-
if (existsSync(blobPath) && statSync(blobPath).size === file.size) {
|
|
84
|
-
completeFiles++;
|
|
85
|
-
continue;
|
|
86
|
-
}
|
|
87
|
-
const partPath = `${blobPath}.incomplete`;
|
|
88
|
-
const partBytes = existsSync(partPath) ? statSync(partPath).size : 0;
|
|
89
|
-
const credited = partBytes >= 0 && partBytes <= file.size ? partBytes : 0;
|
|
90
|
-
resumableBytes += credited;
|
|
91
|
-
remainingBytes += file.size - credited;
|
|
92
|
-
remainingFiles++;
|
|
93
|
-
}
|
|
94
|
-
return { remainingBytes, remainingFiles, completeFiles, resumableBytes };
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
function formatGib(bytes: number): string {
|
|
98
|
-
return `${(bytes / 2 ** 30).toFixed(2)} GiB`;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/** HF auth: explicit > env > the file `hf auth login` writes. */
|
|
102
|
-
export function hfToken(): string | null {
|
|
103
|
-
if (process.env.HF_TOKEN) return process.env.HF_TOKEN;
|
|
104
|
-
try {
|
|
105
|
-
return readFileSync(`${process.env.HOME}/.cache/huggingface/token`, "utf8").trim() || null;
|
|
106
|
-
} catch {
|
|
107
|
-
return null;
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/** 401/403 almost always means a gated/private repo, not a broken network —
|
|
112
|
-
* say so instead of leaving the user staring at a bare status code. */
|
|
113
|
-
function authHint(status: number): string {
|
|
114
|
-
return status === 401 || status === 403
|
|
115
|
-
? " — gated repo? run `hf auth login` or set HF_TOKEN"
|
|
116
|
-
: "";
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// --- download lockfile -----------------------------------------------------
|
|
120
|
-
// Two writers on one blob (a foreground `mlx-bun get` racing the server's
|
|
121
|
-
// background auto-download) both append to the same .incomplete file and
|
|
122
|
-
// corrupt it. An O_EXCL .lock beside the .incomplete serializes them; a lock
|
|
123
|
-
// older than LOCK_STALE_MS (crashed owner) or whose pid is gone is stolen.
|
|
124
|
-
|
|
125
|
-
const LOCK_STALE_MS = 60 * 60 * 1000; // ~1 h: longer than any sane blob fetch
|
|
126
|
-
|
|
127
|
-
function lockIsStale(lockPath: string): boolean {
|
|
128
|
-
try {
|
|
129
|
-
const st = statSync(lockPath);
|
|
130
|
-
if (Date.now() - st.mtimeMs > LOCK_STALE_MS) return true;
|
|
131
|
-
const pid = Number(readFileSync(lockPath, "utf8").split("\n")[0]);
|
|
132
|
-
if (Number.isFinite(pid) && pid > 0) {
|
|
133
|
-
try { process.kill(pid, 0); } catch { return true; } // owner is gone
|
|
134
|
-
}
|
|
135
|
-
return false;
|
|
136
|
-
} catch {
|
|
137
|
-
return true; // unreadable/vanished — treat as stale, O_EXCL still arbitrates
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/** Take `<blobPath>.lock` (O_EXCL). Returns a release fn, or throws the
|
|
142
|
-
* friendly "another download in progress" error when live-held. */
|
|
143
|
-
function acquireLock(blobPath: string, name: string): () => void {
|
|
144
|
-
const lockPath = `${blobPath}.lock`;
|
|
145
|
-
for (let attempt = 0; attempt < 2; attempt++) {
|
|
146
|
-
try {
|
|
147
|
-
const fd = openSync(lockPath, "wx");
|
|
148
|
-
writeSync(fd, `${process.pid}\n${new Date().toISOString()}\n`);
|
|
149
|
-
closeSync(fd);
|
|
150
|
-
return () => { try { rmSync(lockPath, { force: true }); } catch {} };
|
|
151
|
-
} catch (e) {
|
|
152
|
-
if ((e as NodeJS.ErrnoException).code !== "EEXIST") throw e;
|
|
153
|
-
if (lockIsStale(lockPath)) {
|
|
154
|
-
try { rmSync(lockPath, { force: true }); } catch {}
|
|
155
|
-
continue; // one retry after stealing
|
|
156
|
-
}
|
|
157
|
-
let holder = "";
|
|
158
|
-
try { holder = ` (pid ${readFileSync(lockPath, "utf8").split("\n")[0]})`; } catch {}
|
|
159
|
-
throw new Error(
|
|
160
|
-
`another download of ${name} is in progress${holder} — ` +
|
|
161
|
-
`wait for it to finish, or delete ${lockPath} if you're sure it isn't`,
|
|
162
|
-
);
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
throw new Error(`could not acquire ${lockPath}`);
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
/** Reject a repo file path that could escape the snapshot directory once
|
|
169
|
-
* joined onto it. `rfilename` comes verbatim from the remote HF API's
|
|
170
|
-
* `siblings[].rfilename` (listRepoFiles) — for a repo the caller doesn't
|
|
171
|
-
* control (attacker-owned/attacker-referenced), that string is untrusted
|
|
172
|
-
* input reaching a path-join + symlink write (downloadModel's
|
|
173
|
-
* `join(snapDir, f.rfilename)` + `symlinkSync`). node:path's `join()`
|
|
174
|
-
* normalizes `..` segments, so an rfilename like
|
|
175
|
-
* `../../../../home/x/.ssh/authorized_keys` would otherwise resolve
|
|
176
|
-
* OUTSIDE the intended snapshots dir and write a symlink at an arbitrary
|
|
177
|
-
* filesystem path. Reachable over HTTP via POST /api/hub/download
|
|
178
|
-
* (src/hub-rest.ts), which passes any caller-supplied `repo` straight
|
|
179
|
-
* through with no allowlist — this check is the actual gate, not that
|
|
180
|
-
* route. A leading `/` (absolute path) is equally rejected: join() would
|
|
181
|
-
* otherwise treat it as relative to snapDir and still normalize safely,
|
|
182
|
-
* but there's no legitimate reason a repo file entry needs one, and
|
|
183
|
-
* rejecting it removes any doubt. */
|
|
184
|
-
export function isSafeRepoFilename(rfilename: string): boolean {
|
|
185
|
-
if (!rfilename || rfilename.startsWith("/") || rfilename.includes("\0")) return false;
|
|
186
|
-
const parts = rfilename.split("/");
|
|
187
|
-
return parts.every((p) => p !== "" && p !== "." && p !== "..");
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
/** git blob identity: sha1("blob <size>\0" + content) — what blobId is. */
|
|
191
|
-
export function gitBlobSha1(bytes: Uint8Array): string {
|
|
192
|
-
const hasher = new Bun.CryptoHasher("sha1");
|
|
193
|
-
hasher.update(`blob ${bytes.length}\0`);
|
|
194
|
-
hasher.update(bytes);
|
|
195
|
-
return hasher.digest("hex");
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
export async function listRepoFiles(
|
|
199
|
-
repoId: string, opts: DownloadOptions = {},
|
|
200
|
-
): Promise<RepoListing> {
|
|
201
|
-
const endpoint = opts.endpoint ?? ENDPOINT;
|
|
202
|
-
const revision = opts.revision ?? "main";
|
|
203
|
-
const token = opts.token === undefined ? hfToken() : opts.token;
|
|
204
|
-
const url = `${endpoint}/api/models/${repoId}/revision/${encodeURIComponent(revision)}?blobs=true`;
|
|
205
|
-
const res = await fetch(url, {
|
|
206
|
-
headers: token ? { authorization: `Bearer ${token}` } : {},
|
|
207
|
-
});
|
|
208
|
-
if (!res.ok)
|
|
209
|
-
throw new Error(
|
|
210
|
-
`HF API ${res.status} for ${repoId}@${revision}: ${(await res.text()).slice(0, 200)}` +
|
|
211
|
-
authHint(res.status),
|
|
212
|
-
);
|
|
213
|
-
const body = (await res.json()) as {
|
|
214
|
-
sha: string;
|
|
215
|
-
siblings: {
|
|
216
|
-
rfilename: string;
|
|
217
|
-
size?: number;
|
|
218
|
-
blobId?: string;
|
|
219
|
-
// The current HF API calls this field `sha256`. Accept the older `oid`
|
|
220
|
-
// spelling too, but normalize it before the download path sees it.
|
|
221
|
-
lfs?: { sha256?: string; oid?: string; size: number };
|
|
222
|
-
}[];
|
|
223
|
-
};
|
|
224
|
-
return {
|
|
225
|
-
sha: body.sha,
|
|
226
|
-
files: body.siblings.map((s) => {
|
|
227
|
-
let lfs: RepoFile["lfs"];
|
|
228
|
-
if (s.lfs) {
|
|
229
|
-
const sha256 = s.lfs.sha256 ?? s.lfs.oid;
|
|
230
|
-
if (!sha256 || !/^[0-9a-f]{64}$/i.test(sha256))
|
|
231
|
-
throw new Error(`HF API returned an invalid LFS sha256 for ${s.rfilename}`);
|
|
232
|
-
lfs = { sha256: sha256.toLowerCase(), size: s.lfs.size };
|
|
233
|
-
}
|
|
234
|
-
if (!lfs && (!s.blobId || !/^[0-9a-f]{40}$/.test(s.blobId)))
|
|
235
|
-
throw new Error(`HF API returned an invalid git blob SHA-1 for ${s.rfilename}`);
|
|
236
|
-
return {
|
|
237
|
-
rfilename: s.rfilename,
|
|
238
|
-
size: lfs?.size ?? s.size ?? 0,
|
|
239
|
-
blobId: s.blobId ?? "",
|
|
240
|
-
lfs,
|
|
241
|
-
};
|
|
242
|
-
}),
|
|
243
|
-
};
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
/** Follow redirects manually so the Authorization header is DROPPED at
|
|
247
|
-
* the CDN hop — presigned S3/CloudFront URLs reject requests that
|
|
248
|
-
* carry it (the classic hub-client footgun). Range survives. */
|
|
249
|
-
async function fetchBlob(
|
|
250
|
-
url: string, token: string | null, rangeStart: number,
|
|
251
|
-
): Promise<Response> {
|
|
252
|
-
const range: Record<string, string> = {};
|
|
253
|
-
if (rangeStart > 0) range["range"] = `bytes=${rangeStart}-`;
|
|
254
|
-
const authed: Record<string, string> = { ...range };
|
|
255
|
-
if (token) authed["authorization"] = `Bearer ${token}`;
|
|
256
|
-
let res = await fetch(url, { redirect: "manual", headers: authed });
|
|
257
|
-
let hops = 0;
|
|
258
|
-
while (res.status >= 300 && res.status < 400 && hops < 5) {
|
|
259
|
-
const loc = res.headers.get("location");
|
|
260
|
-
if (!loc) break;
|
|
261
|
-
// no auth past the redirect — presigned URLs reject it
|
|
262
|
-
res = await fetch(new URL(loc, url).href, { redirect: "manual", headers: range });
|
|
263
|
-
hops++;
|
|
264
|
-
}
|
|
265
|
-
return res;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
export async function downloadOne(
|
|
269
|
-
url: string, token: string | null, blobPath: string,
|
|
270
|
-
expected: { size: number; sha256?: string; sha1?: string; name: string },
|
|
271
|
-
onProgress?: DownloadOptions["onProgress"],
|
|
272
|
-
): Promise<void> {
|
|
273
|
-
// Serialize concurrent writers (foreground get vs background auto-download)
|
|
274
|
-
// on this blob — both appending to one .incomplete corrupts it.
|
|
275
|
-
const release = acquireLock(blobPath, expected.name);
|
|
276
|
-
try {
|
|
277
|
-
await downloadOneLocked(url, token, blobPath, expected, onProgress);
|
|
278
|
-
} finally {
|
|
279
|
-
release();
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
async function downloadOneLocked(
|
|
284
|
-
url: string, token: string | null, blobPath: string,
|
|
285
|
-
expected: { size: number; sha256?: string; sha1?: string; name: string },
|
|
286
|
-
onProgress?: DownloadOptions["onProgress"],
|
|
287
|
-
): Promise<void> {
|
|
288
|
-
const partPath = `${blobPath}.incomplete`;
|
|
289
|
-
let offset = existsSync(partPath) ? statSync(partPath).size : 0;
|
|
290
|
-
if (offset > expected.size) {
|
|
291
|
-
rmSync(partPath); // stale junk; start over
|
|
292
|
-
offset = 0;
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
// Streaming verification — resume re-hashes the existing prefix so the
|
|
296
|
-
// final digest always covers every byte on disk. Chunked reads: a resumed
|
|
297
|
-
// multi-GB blob must not be materialized as one allocation.
|
|
298
|
-
const sha256 = new Bun.CryptoHasher("sha256");
|
|
299
|
-
const sha1 = new Bun.CryptoHasher("sha1");
|
|
300
|
-
sha1.update(`blob ${expected.size}\0`);
|
|
301
|
-
if (offset > 0) {
|
|
302
|
-
const fd = openSync(partPath, "r");
|
|
303
|
-
try {
|
|
304
|
-
const buf = Buffer.alloc(8 * 2 ** 20);
|
|
305
|
-
let pos = 0;
|
|
306
|
-
while (pos < offset) {
|
|
307
|
-
const n = readSync(fd, buf, 0, Math.min(buf.length, offset - pos), pos);
|
|
308
|
-
if (n <= 0) break;
|
|
309
|
-
const chunk = buf.subarray(0, n);
|
|
310
|
-
sha256.update(chunk);
|
|
311
|
-
sha1.update(chunk);
|
|
312
|
-
pos += n;
|
|
313
|
-
}
|
|
314
|
-
} finally {
|
|
315
|
-
closeSync(fd);
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
if (offset < expected.size) {
|
|
320
|
-
const res = await fetchBlob(url, token, offset);
|
|
321
|
-
if (res.status === 200 && offset > 0) {
|
|
322
|
-
// server ignored Range — start the file (and hashes) over
|
|
323
|
-
rmSync(partPath);
|
|
324
|
-
return downloadOneLocked(url, token, blobPath, expected, onProgress);
|
|
325
|
-
}
|
|
326
|
-
if (res.status !== 200 && res.status !== 206)
|
|
327
|
-
throw new Error(`download failed (${res.status}) for ${expected.name}${authHint(res.status)}`);
|
|
328
|
-
const out = createWriteStream(partPath, { flags: offset > 0 ? "a" : "w" });
|
|
329
|
-
try {
|
|
330
|
-
for await (const chunk of res.body as ReadableStream<Uint8Array>) {
|
|
331
|
-
sha256.update(chunk);
|
|
332
|
-
sha1.update(chunk);
|
|
333
|
-
await new Promise<void>((resolve, reject) =>
|
|
334
|
-
out.write(chunk, (e) => (e ? reject(e) : resolve())),
|
|
335
|
-
);
|
|
336
|
-
offset += chunk.length;
|
|
337
|
-
onProgress?.(expected.name, offset, expected.size);
|
|
338
|
-
}
|
|
339
|
-
} finally {
|
|
340
|
-
await new Promise<void>((resolve) => out.end(() => resolve()));
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
if (expected.size === 0 && !existsSync(partPath)) await Bun.write(partPath, "");
|
|
345
|
-
if (offset !== expected.size) {
|
|
346
|
-
throw new Error(
|
|
347
|
-
`short download for ${expected.name}: ${offset} of ${expected.size} bytes ` +
|
|
348
|
-
`(.incomplete kept — rerun to resume)`,
|
|
349
|
-
);
|
|
350
|
-
}
|
|
351
|
-
const digestOk = expected.sha256
|
|
352
|
-
? sha256.digest("hex") === expected.sha256
|
|
353
|
-
: expected.sha1
|
|
354
|
-
? sha1.digest("hex") === expected.sha1
|
|
355
|
-
: true;
|
|
356
|
-
if (!digestOk) {
|
|
357
|
-
rmSync(partPath); // corrupt — never resume from it
|
|
358
|
-
throw new Error(`checksum mismatch for ${expected.name} — partial removed, rerun to retry`);
|
|
359
|
-
}
|
|
360
|
-
renameSync(partPath, blobPath);
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
/** Download (or complete) a model snapshot. Returns the snapshot path.
|
|
364
|
-
* Idempotent: verified blobs are never re-fetched; partial blobs
|
|
365
|
-
* resume with a Range request. */
|
|
366
|
-
/** Process-global download tracker — the status page's /downloads
|
|
367
|
-
* endpoint reads this. Only downloads performed by THIS process are
|
|
368
|
-
* visible (a `mlx-bun get` in another terminal is not). */
|
|
369
|
-
export interface DownloadStatus {
|
|
370
|
-
repoId: string;
|
|
371
|
-
state: "active" | "done" | "error";
|
|
372
|
-
currentFile: string | null;
|
|
373
|
-
receivedBytes: number;
|
|
374
|
-
totalBytes: number;
|
|
375
|
-
filesDone: number;
|
|
376
|
-
filesTotal: number;
|
|
377
|
-
/** Rolling-window transfer rate (server-measured, ~5 s window). */
|
|
378
|
-
bytesPerSec: number;
|
|
379
|
-
startedAt: number;
|
|
380
|
-
finishedAt: number | null;
|
|
381
|
-
error?: string;
|
|
382
|
-
}
|
|
383
|
-
const downloadLog: DownloadStatus[] = [];
|
|
384
|
-
export function downloadsSnapshot(): DownloadStatus[] {
|
|
385
|
-
return downloadLog.slice(-5);
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
/** True when a download for this exact repoId is currently "active" in this
|
|
389
|
-
* process's tracker (used by the Hub's POST /api/hub/download to refuse a
|
|
390
|
-
* duplicate kick-off — same repoId, still running — rather than starting a
|
|
391
|
-
* second writer that would race the first on the same blob lockfile). */
|
|
392
|
-
export function isDownloadActive(repoId: string): boolean {
|
|
393
|
-
return downloadLog.some((d) => d.repoId === repoId && d.state === "active");
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
export async function downloadModel(
|
|
397
|
-
repoId: string, opts: DownloadOptions = {},
|
|
398
|
-
): Promise<string> {
|
|
399
|
-
const endpoint = opts.endpoint ?? ENDPOINT;
|
|
400
|
-
const revision = opts.revision ?? "main";
|
|
401
|
-
const token = opts.token === undefined ? hfToken() : opts.token;
|
|
402
|
-
const hub = opts.cacheDir ?? DEFAULT_HUB;
|
|
403
|
-
|
|
404
|
-
const listing = await listRepoFiles(repoId, { ...opts, token });
|
|
405
|
-
// Fail the whole download closed (not skip-and-continue) if the repo's
|
|
406
|
-
// file listing contains a path-traversal rfilename — see
|
|
407
|
-
// isSafeRepoFilename's doc comment. This gates BOTH the CLI and the web
|
|
408
|
-
// Hub's POST /api/hub/download (src/hub-rest.ts), which accepts any
|
|
409
|
-
// caller-supplied repo string with no allowlist of its own.
|
|
410
|
-
for (const f of listing.files) {
|
|
411
|
-
if (!isSafeRepoFilename(f.rfilename)) {
|
|
412
|
-
throw new Error(`${repoId}: refusing unsafe file path in repo listing: ${JSON.stringify(f.rfilename)}`);
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
const repoDir = join(hub, `models--${repoId.replaceAll("/", "--")}`);
|
|
416
|
-
const blobsDir = join(repoDir, "blobs");
|
|
417
|
-
const snapDir = join(repoDir, "snapshots", listing.sha);
|
|
418
|
-
mkdirSync(blobsDir, { recursive: true });
|
|
419
|
-
mkdirSync(join(repoDir, "refs"), { recursive: true });
|
|
420
|
-
|
|
421
|
-
// Preflight before the first network payload. This matters for direct
|
|
422
|
-
// Colibri artifacts (hundreds of GiB): discovering a full volume after the
|
|
423
|
-
// 140th shard is not a usable failure mode. Existing verified blobs and
|
|
424
|
-
// partial prefixes are credited exactly, so rerunning after interruption is
|
|
425
|
-
// both resumable and progressively cheaper.
|
|
426
|
-
const space = planDownloadSpace(listing.files, blobsDir);
|
|
427
|
-
if (space.remainingBytes > 0) {
|
|
428
|
-
const fs = statfsSync(blobsDir);
|
|
429
|
-
const availableBytes = fs.bavail * fs.bsize;
|
|
430
|
-
const requiredBytes = space.remainingBytes + DOWNLOAD_DISK_RESERVE_BYTES;
|
|
431
|
-
if (requiredBytes > availableBytes) {
|
|
432
|
-
throw new Error(
|
|
433
|
-
`insufficient disk space for ${repoId}: ${formatGib(space.remainingBytes)} ` +
|
|
434
|
-
`remains across ${space.remainingFiles} files, but only ` +
|
|
435
|
-
`${formatGib(availableBytes)} is available in ${hub} ` +
|
|
436
|
-
`(${formatGib(DOWNLOAD_DISK_RESERVE_BYTES)} safety reserve). ` +
|
|
437
|
-
`Free space or choose another cache directory, then rerun; ` +
|
|
438
|
-
`${formatGib(space.resumableBytes)} of partial data will resume.`,
|
|
439
|
-
);
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
const status: DownloadStatus = {
|
|
444
|
-
repoId, state: "active", currentFile: null,
|
|
445
|
-
receivedBytes: 0, totalBytes: listing.files.reduce((a, f) => a + f.size, 0),
|
|
446
|
-
filesDone: 0, filesTotal: listing.files.length,
|
|
447
|
-
bytesPerSec: 0, startedAt: Date.now(), finishedAt: null,
|
|
448
|
-
};
|
|
449
|
-
downloadLog.push(status);
|
|
450
|
-
let doneBytes = 0;
|
|
451
|
-
// Rolling ~5 s window of (time, receivedBytes) samples for the rate.
|
|
452
|
-
const samples: Array<[number, number]> = [];
|
|
453
|
-
const sampleRate = (bytes: number) => {
|
|
454
|
-
const now = performance.now();
|
|
455
|
-
samples.push([now, bytes]);
|
|
456
|
-
while (samples.length > 2 && now - samples[0]![0] > 5000) samples.shift();
|
|
457
|
-
const [t0, b0] = samples[0]!;
|
|
458
|
-
status.bytesPerSec = now > t0 ? ((bytes - b0) / (now - t0)) * 1000 : 0;
|
|
459
|
-
};
|
|
460
|
-
|
|
461
|
-
try {
|
|
462
|
-
for (const f of listing.files) {
|
|
463
|
-
const blobId = f.lfs?.sha256 ?? f.blobId;
|
|
464
|
-
if (!blobId) throw new Error(`no blob id for ${f.rfilename} (API response missing ?blobs=true data)`);
|
|
465
|
-
const blobPath = join(blobsDir, blobId);
|
|
466
|
-
|
|
467
|
-
if (!existsSync(blobPath) || statSync(blobPath).size !== f.size) {
|
|
468
|
-
const url = `${endpoint}/${repoId}/resolve/${listing.sha}/${f.rfilename}`;
|
|
469
|
-
status.currentFile = f.rfilename;
|
|
470
|
-
await downloadOne(url, token, blobPath, {
|
|
471
|
-
size: f.size, name: f.rfilename,
|
|
472
|
-
...(f.lfs ? { sha256: f.lfs.sha256 } : { sha1: f.blobId }),
|
|
473
|
-
}, (file, received, total) => {
|
|
474
|
-
status.receivedBytes = doneBytes + received;
|
|
475
|
-
sampleRate(status.receivedBytes);
|
|
476
|
-
opts.onProgress?.(file, received, total);
|
|
477
|
-
});
|
|
478
|
-
}
|
|
479
|
-
doneBytes += f.size;
|
|
480
|
-
status.receivedBytes = doneBytes;
|
|
481
|
-
status.filesDone++;
|
|
482
|
-
status.currentFile = null;
|
|
483
|
-
|
|
484
|
-
const linkPath = join(snapDir, f.rfilename);
|
|
485
|
-
mkdirSync(dirname(linkPath), { recursive: true });
|
|
486
|
-
if (!existsSync(linkPath)) {
|
|
487
|
-
// relative target, depth-aware for nested rfilenames
|
|
488
|
-
const depth = f.rfilename.split("/").length - 1;
|
|
489
|
-
symlinkSync(join("../".repeat(depth + 2), "blobs", blobId), linkPath);
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
status.state = "done";
|
|
493
|
-
status.bytesPerSec = 0;
|
|
494
|
-
status.finishedAt = Date.now();
|
|
495
|
-
} catch (e) {
|
|
496
|
-
status.state = "error";
|
|
497
|
-
status.bytesPerSec = 0;
|
|
498
|
-
status.error = e instanceof Error ? e.message : String(e);
|
|
499
|
-
status.finishedAt = Date.now();
|
|
500
|
-
throw e;
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
await Bun.write(join(repoDir, "refs", revision), listing.sha);
|
|
504
|
-
return snapDir;
|
|
505
|
-
}
|
package/src/embed.ts
DELETED
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
// Text-embedding entry point. Wraps Qwen3Model.embedPooled (last-token hidden →
|
|
2
|
-
// L2-normalized vector) with the Qwen3-Embedding input convention so the CLI
|
|
3
|
-
// (`mlx-bun embed`), the server (`/v1/embeddings`), and in-repo experiments all
|
|
4
|
-
// produce the SAME vectors — the ones verified bit-exact vs mlx-lm in
|
|
5
|
-
// tests/parity/qwen3-embed-parity.test.ts.
|
|
6
|
-
|
|
7
|
-
import { Qwen3Model } from "./model/qwen3";
|
|
8
|
-
import type { RuntimeModel } from "./model/factory";
|
|
9
|
-
import type { LoadedTokenizer } from "./tokenizer";
|
|
10
|
-
import * as ops from "./mlx/ops";
|
|
11
|
-
|
|
12
|
-
/** <|endoftext|> — the token Qwen3-Embedding terminates every input with and
|
|
13
|
-
* pools the last position of. */
|
|
14
|
-
export const EMBED_EOD = 151643;
|
|
15
|
-
|
|
16
|
-
/** Only plain-Qwen3 (the Qwen3-Embedding backbone) exposes a pooled-embedding
|
|
17
|
-
* path today. Narrow the served RuntimeModel before embedding. */
|
|
18
|
-
export function isEmbeddingModel(model: RuntimeModel): model is Qwen3Model {
|
|
19
|
-
return model instanceof Qwen3Model;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/** Qwen3-Embedding query format: a task instruction steers WHICH similarity axis
|
|
23
|
-
* the geometry reflects ("represent this for topic routing" vs "...for
|
|
24
|
-
* sentiment"). Documents are embedded raw; only queries get an instruction. */
|
|
25
|
-
export function withInstruction(text: string, instruction?: string): string {
|
|
26
|
-
return instruction ? `Instruct: ${instruction}\nQuery:${text}` : text;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/** Tripwire: counts embedOne invocations (embedMany accrues via embedOne) so
|
|
30
|
-
* callers can assert the pipeline embeds exactly as many times as expected. */
|
|
31
|
-
let embedCallCount = 0;
|
|
32
|
-
|
|
33
|
-
/** Reset the embed tripwire counter to 0. */
|
|
34
|
-
export function resetEmbedCounter(): void {
|
|
35
|
-
embedCallCount = 0;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/** Read the embed tripwire counter (total embedOne calls since last reset). */
|
|
39
|
-
export function getEmbedCounter(): number {
|
|
40
|
-
return embedCallCount;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export interface EmbedResult {
|
|
44
|
-
/** L2-normalized embedding (length = model hidden size). */
|
|
45
|
-
vector: Float32Array;
|
|
46
|
-
/** Token count of the embedded input (incl. the EOD pooling token). */
|
|
47
|
-
tokens: number;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/** Embed one text. `instruction` (optional) applies the query format. */
|
|
51
|
-
export function embedOne(
|
|
52
|
-
model: Qwen3Model,
|
|
53
|
-
tok: LoadedTokenizer,
|
|
54
|
-
text: string,
|
|
55
|
-
instruction?: string,
|
|
56
|
-
): EmbedResult {
|
|
57
|
-
embedCallCount++;
|
|
58
|
-
// addSpecialTokens=false matches the bit-exact parity path (Qwen3 has no BOS;
|
|
59
|
-
// we append the EOD pooling token ourselves).
|
|
60
|
-
const ids = [...tok.encode(withInstruction(text, instruction), false), EMBED_EOD];
|
|
61
|
-
const idArr = ops.fromInt32(ids, [1, ids.length]);
|
|
62
|
-
const vec = model.embedPooled(idArr);
|
|
63
|
-
idArr.dispose();
|
|
64
|
-
const out = vec.toFloat32();
|
|
65
|
-
vec.dispose();
|
|
66
|
-
return { vector: out, tokens: ids.length };
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/** Embed many texts (one forward each — the runtime is single-sequence). */
|
|
70
|
-
export function embedMany(
|
|
71
|
-
model: Qwen3Model,
|
|
72
|
-
tok: LoadedTokenizer,
|
|
73
|
-
texts: string[],
|
|
74
|
-
instruction?: string,
|
|
75
|
-
): EmbedResult[] {
|
|
76
|
-
return texts.map((t) => embedOne(model, tok, t, instruction));
|
|
77
|
-
}
|
package/src/engine/admission.ts
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import type { Cancellation } from "../contracts/generation";
|
|
2
|
-
import type { DisposableResource } from "../contracts/resources";
|
|
3
|
-
import { throwIfCancelled } from "./cancellation";
|
|
4
|
-
|
|
5
|
-
export class AdmissionRejected extends Error {
|
|
6
|
-
constructor() { super("admission queue is full"); this.name = "AdmissionRejected"; }
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
/** FIFO reservations. A cancelled waiter releases no active owner's capacity. */
|
|
10
|
-
export class AdmissionPool {
|
|
11
|
-
#active = 0;
|
|
12
|
-
#closed = false;
|
|
13
|
-
#waiting: Array<{ resolve(lease: DisposableResource): void; reject(error: unknown): void; unsubscribe(): void }> = [];
|
|
14
|
-
constructor(readonly capacity: number, readonly maxQueued = 64) {
|
|
15
|
-
if (!Number.isSafeInteger(capacity) || capacity < 1 || !Number.isSafeInteger(maxQueued) || maxQueued < 0)
|
|
16
|
-
throw new Error("invalid admission capacity");
|
|
17
|
-
}
|
|
18
|
-
get active(): number { return this.#active; }
|
|
19
|
-
get queued(): number { return this.#waiting.length; }
|
|
20
|
-
|
|
21
|
-
acquire(cancellation?: Cancellation): Promise<DisposableResource> {
|
|
22
|
-
if (this.#closed) return Promise.reject(new Error("admission is closed"));
|
|
23
|
-
try { if (cancellation) throwIfCancelled(cancellation); }
|
|
24
|
-
catch (error) { return Promise.reject(error); }
|
|
25
|
-
if (this.#active < this.capacity) return Promise.resolve(this.#lease());
|
|
26
|
-
if (this.#waiting.length >= this.maxQueued) return Promise.reject(new AdmissionRejected());
|
|
27
|
-
return new Promise((resolve, reject) => {
|
|
28
|
-
const waiter = { resolve, reject, unsubscribe() {} };
|
|
29
|
-
this.#waiting.push(waiter);
|
|
30
|
-
waiter.unsubscribe = cancellation?.subscribe((reason) => {
|
|
31
|
-
const index = this.#waiting.indexOf(waiter);
|
|
32
|
-
if (index < 0) return;
|
|
33
|
-
this.#waiting.splice(index, 1);
|
|
34
|
-
waiter.unsubscribe();
|
|
35
|
-
reject(new Error(`admission cancelled: ${reason}`));
|
|
36
|
-
}) ?? (() => {});
|
|
37
|
-
if (!this.#waiting.includes(waiter)) waiter.unsubscribe();
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
#lease(): DisposableResource {
|
|
42
|
-
this.#active++;
|
|
43
|
-
let released = false;
|
|
44
|
-
return { dispose: () => {
|
|
45
|
-
if (released) return;
|
|
46
|
-
released = true;
|
|
47
|
-
this.#active--;
|
|
48
|
-
const next = this.#waiting.shift();
|
|
49
|
-
if (next) { next.unsubscribe(); next.resolve(this.#lease()); }
|
|
50
|
-
} };
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
close(): void {
|
|
54
|
-
this.#closed = true;
|
|
55
|
-
for (const waiter of this.#waiting.splice(0)) {
|
|
56
|
-
waiter.unsubscribe(); waiter.reject(new Error("admission is closed"));
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
}
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import type { CancelReason, Cancellation } from "../contracts/generation";
|
|
2
|
-
|
|
3
|
-
export class GenerationCancelled extends Error {
|
|
4
|
-
constructor(readonly reason: CancelReason) {
|
|
5
|
-
super(`generation cancelled: ${reason}`);
|
|
6
|
-
this.name = "GenerationCancelled";
|
|
7
|
-
}
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export function throwIfCancelled(cancellation: Cancellation): void {
|
|
11
|
-
if (cancellation.reason !== undefined) throw new GenerationCancelled(cancellation.reason);
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export class CancellationSource implements Cancellation {
|
|
15
|
-
#reason: CancelReason | undefined;
|
|
16
|
-
#listeners = new Set<(reason: CancelReason) => void>();
|
|
17
|
-
get reason(): CancelReason | undefined { return this.#reason; }
|
|
18
|
-
|
|
19
|
-
subscribe(listener: (reason: CancelReason) => void): () => void {
|
|
20
|
-
if (this.#reason !== undefined) {
|
|
21
|
-
listener(this.#reason);
|
|
22
|
-
return () => {};
|
|
23
|
-
}
|
|
24
|
-
this.#listeners.add(listener);
|
|
25
|
-
return () => { this.#listeners.delete(listener); };
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
cancel(reason: CancelReason): void {
|
|
29
|
-
if (this.#reason !== undefined) return;
|
|
30
|
-
this.#reason = reason;
|
|
31
|
-
const listeners = [...this.#listeners];
|
|
32
|
-
this.#listeners.clear();
|
|
33
|
-
// A faulty observer cannot prevent the remaining owners from cancelling.
|
|
34
|
-
for (const listener of listeners) {
|
|
35
|
-
try { listener(reason); } catch { /* Observers do not own settlement. */ }
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
}
|