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
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import type { ModelBinding } from "../engine/model-binding";
|
|
2
|
+
import type { LoadedModelContext } from "../engine/model-host";
|
|
3
|
+
import type { PreparationExecutor } from "../engine/preparation";
|
|
4
|
+
import type { ModelPromptBuilder } from "./prompt-contracts";
|
|
5
|
+
import { modelPromptBuilder } from "./prompt-builder";
|
|
6
|
+
import { createRequestPrep } from "./request-prep";
|
|
7
|
+
import { RequestOwnership } from "./request-plan";
|
|
8
|
+
import { RequestError } from "./pipeline";
|
|
9
|
+
import { errorResponse } from "./http";
|
|
10
|
+
import { readDecisionBody } from "./decision-body";
|
|
11
|
+
import { decisionAnswer, decisionCandidates, decisionMessages, DecisionValidationError,
|
|
12
|
+
parseJevDecision, parseOpenAiDecision } from "./decision-wire";
|
|
13
|
+
|
|
14
|
+
export function createDecisionRoutes(engine: {
|
|
15
|
+
context: LoadedModelContext;
|
|
16
|
+
binding: Pick<ModelBinding, "decide"> & Partial<Pick<ModelBinding, "gateway">>;
|
|
17
|
+
preparation: PreparationExecutor;
|
|
18
|
+
}, options: { contextLimit: number | null; defaultAdapter?: string; buildPrompt?: ModelPromptBuilder }) {
|
|
19
|
+
const ctx = engine.context, build = options.buildPrompt ?? modelPromptBuilder(ctx);
|
|
20
|
+
const prep = createRequestPrep({ ctx, serverOptions: { defaultThinking: false }, defaultGeneratedTokens: undefined });
|
|
21
|
+
return {
|
|
22
|
+
async handle(request: Request): Promise<Response | null> {
|
|
23
|
+
const path = new URL(request.url).pathname;
|
|
24
|
+
if (request.method !== "POST") return null;
|
|
25
|
+
const jev = path === "/v1/systemone";
|
|
26
|
+
if (!jev && path !== "/v1/decisions") return null;
|
|
27
|
+
const requestOwnership = new RequestOwnership();
|
|
28
|
+
try {
|
|
29
|
+
const reservation = await engine.preparation.reserve?.("decision", request.signal);
|
|
30
|
+
if (reservation) requestOwnership.retain(reservation);
|
|
31
|
+
const body = await readDecisionBody(request);
|
|
32
|
+
const parsed = jev ? parseJevDecision(body) : parseOpenAiDecision(body);
|
|
33
|
+
const decide = engine.binding.decide?.bind(engine.binding);
|
|
34
|
+
if (!decide) throw new RequestError(501, `model "${ctx.modelId}" declares no decision scoring operation`,
|
|
35
|
+
{ message: `model "${ctx.modelId}" declares no decision scoring operation`, type: "not_implemented", code: "unsupported_execution" });
|
|
36
|
+
const schedule = <T>(work: () => Promise<T>): Promise<T> => engine.preparation.run(work, request.signal);
|
|
37
|
+
request.signal.throwIfAborted();
|
|
38
|
+
const ending = decisionAnswerEnding(ctx, prep);
|
|
39
|
+
const answers: Record<string, unknown>[] = [];
|
|
40
|
+
let inputTokens = 0;
|
|
41
|
+
for (const question of parsed.questions) {
|
|
42
|
+
request.signal.throwIfAborted();
|
|
43
|
+
const candidates = decisionCandidates(ctx.tokenizer, question, ending);
|
|
44
|
+
const ownership = new RequestOwnership();
|
|
45
|
+
try {
|
|
46
|
+
const messages = decisionMessages(parsed.input, question);
|
|
47
|
+
const media = messages.some(message => Array.isArray(message.content) && message.content.some(part => part.type !== "text"));
|
|
48
|
+
if (media) {
|
|
49
|
+
const reservation = await engine.preparation.reserve?.("media", request.signal);
|
|
50
|
+
if (reservation) ownership.retain(reservation);
|
|
51
|
+
}
|
|
52
|
+
let built: Awaited<ReturnType<ModelPromptBuilder>>;
|
|
53
|
+
try {
|
|
54
|
+
built = await build({ messages, reasoning_effort: "none", chat_template_kwargs: { enable_thinking: false } },
|
|
55
|
+
null, ownership, prep, schedule);
|
|
56
|
+
} catch (error) {
|
|
57
|
+
if (error instanceof RequestError) throw error;
|
|
58
|
+
throw new RequestError(400, `prompt build failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
59
|
+
}
|
|
60
|
+
ownership.own(built.vision?.embeddings);
|
|
61
|
+
ownership.own(built.vision?.imageMask);
|
|
62
|
+
ownership.own(built.vision?.multimodalMask);
|
|
63
|
+
ownership.own(built.diffusionPixels);
|
|
64
|
+
request.signal.throwIfAborted();
|
|
65
|
+
if (built.diffusionPixels) throw new RequestError(501, "pixel-input decision scoring is unavailable for this graph");
|
|
66
|
+
if (options.contextLimit !== null && candidates.some(answer => built.promptIds.length + answer.length - 1 > options.contextLimit!))
|
|
67
|
+
throw new RequestError(400, `decision prompt exceeds the ${options.contextLimit}-token context limit`,
|
|
68
|
+
{ message: "decision prompt exceeds the context limit", type: "invalid_request_error", code: "max_tokens_exceeded" });
|
|
69
|
+
const adapters = options.defaultAdapter ? [options.defaultAdapter] : [];
|
|
70
|
+
const probabilities = await decide({ prompt: built.promptIds, candidates, vision: built.vision, signal: request.signal,
|
|
71
|
+
execute: work => schedule(async () => {
|
|
72
|
+
request.signal.throwIfAborted();
|
|
73
|
+
const restore = engine.binding.gateway?.bindAdapterContext?.(adapters, `decision:${JSON.stringify(adapters)}`).enter();
|
|
74
|
+
try { return await work(); } finally { restore?.(); }
|
|
75
|
+
}) });
|
|
76
|
+
request.signal.throwIfAborted();
|
|
77
|
+
if (probabilities.length !== candidates.length || probabilities.some(p => !Number.isFinite(p) || p < 0 || p > 1) ||
|
|
78
|
+
Math.abs(probabilities.reduce((sum, p) => sum + p, 0) - 1) > 1e-5)
|
|
79
|
+
throw new Error("decision binding returned an invalid probability distribution");
|
|
80
|
+
inputTokens += candidates.reduce((sum, answer) => sum + built.promptIds.length + answer.length - 1, 0);
|
|
81
|
+
answers.push(decisionAnswer(question, probabilities, jev));
|
|
82
|
+
} finally { ownership.dispose(); }
|
|
83
|
+
await new Promise<void>(resolve => setImmediate(resolve));
|
|
84
|
+
}
|
|
85
|
+
request.signal.throwIfAborted();
|
|
86
|
+
return Response.json({ model: ctx.modelId,
|
|
87
|
+
answers: jev ? Object.fromEntries(parsed.questions.map((q, i) => [q.name!, answers[i]])) : answers,
|
|
88
|
+
usage: jev ? { input_tokens: inputTokens, output_tokens: 0 } : {
|
|
89
|
+
input_tokens: inputTokens, input_tokens_details: { cached_tokens: 0, cache_write_tokens: 0 },
|
|
90
|
+
output_tokens: 0, output_tokens_details: { reasoning_tokens: 0 }, total_tokens: inputTokens,
|
|
91
|
+
} });
|
|
92
|
+
} catch (error) {
|
|
93
|
+
if (error instanceof DecisionValidationError) return jev
|
|
94
|
+
? Response.json({ detail: [{ loc: ["body", ...error.field.split(".")], msg: error.message, type: "value_error" }] }, { status: 422 })
|
|
95
|
+
: Response.json({ error: { message: error.message, type: "invalid_request_error", param: error.field, code: null } }, { status: 400 });
|
|
96
|
+
return errorResponse(error, path, undefined, request.signal);
|
|
97
|
+
} finally { requestOwnership.dispose(); }
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Score the template's completed assistant turn through its first stop marker,
|
|
103
|
+
* excluding layout after the marker. Encode answer + ending together so BPE
|
|
104
|
+
* merges at that boundary match the supplied text. */
|
|
105
|
+
function decisionAnswerEnding(ctx: LoadedModelContext, prep: ReturnType<typeof createRequestPrep>): string {
|
|
106
|
+
const stops = [...new Set([...ctx.model.config.eosTokenIds, ...(ctx.tokenizer.eosTokenId === null ? [] : [ctx.tokenizer.eosTokenId])])]
|
|
107
|
+
.map(id => ctx.tokenizer.decode([id], false)).filter(Boolean);
|
|
108
|
+
if (ctx.template) {
|
|
109
|
+
const marker = "mlx_bun_decision_answer_9d041a";
|
|
110
|
+
const messages = [{ role: "user" as const, content: "x" }, { role: "assistant" as const, content: marker }];
|
|
111
|
+
const options = { ...prep.templateOptionsFor({ messages,
|
|
112
|
+
reasoning_effort: "none", chat_template_kwargs: { enable_thinking: false } }, null), addGenerationPrompt: false };
|
|
113
|
+
const afterAnswer = (turns: typeof messages) => {
|
|
114
|
+
const rendered = ctx.template!.render(turns, options), at = rendered.indexOf(marker);
|
|
115
|
+
if (at < 0 || rendered.indexOf(marker, at + marker.length) >= 0)
|
|
116
|
+
throw new RequestError(501, "decision scoring requires a chat template that preserves assistant answers");
|
|
117
|
+
return rendered.slice(at + marker.length);
|
|
118
|
+
};
|
|
119
|
+
const throughStop = (suffix: string) => {
|
|
120
|
+
const first = stops.map(text => ({ text, at: suffix.indexOf(text) })).filter(stop => stop.at >= 0)
|
|
121
|
+
.sort((a, b) => a.at - b.at)[0];
|
|
122
|
+
return first ? suffix.slice(0, first.at + first.text.length) : undefined;
|
|
123
|
+
};
|
|
124
|
+
const suffix = afterAnswer(messages);
|
|
125
|
+
// GLM-style templates close an assistant answer with the next role marker.
|
|
126
|
+
// Only keep that boundary, never the synthetic next user's content.
|
|
127
|
+
const ending = throughStop(suffix) ?? throughStop(afterAnswer([...messages, { role: "user", content: "x" }]));
|
|
128
|
+
if (ending !== undefined) return ending;
|
|
129
|
+
if (suffix.trim()) return suffix;
|
|
130
|
+
}
|
|
131
|
+
const fallback = ctx.tokenizer.eosTokenId ?? ctx.model.config.eosTokenIds[0];
|
|
132
|
+
if (fallback === undefined || fallback === null) throw new RequestError(501, "decision scoring requires an answer terminator token");
|
|
133
|
+
return ctx.tokenizer.decode([fallback], false);
|
|
134
|
+
}
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import type { LoadedTokenizer } from "@mlx-bun/inference/input";
|
|
2
|
+
import type { ChatRequestParams } from "./chat-request";
|
|
3
|
+
|
|
4
|
+
type Messages = ChatRequestParams["messages"];
|
|
5
|
+
export interface DecisionOption { value: string | boolean | number; label: string; description: string; legend?: unknown }
|
|
6
|
+
export interface DecisionQuestion {
|
|
7
|
+
type: "predicate" | "choice" | "score";
|
|
8
|
+
name: string | null;
|
|
9
|
+
instructions: string;
|
|
10
|
+
options: DecisionOption[];
|
|
11
|
+
images?: { reference: string; url: string }[];
|
|
12
|
+
}
|
|
13
|
+
export interface DecisionRequest {
|
|
14
|
+
model: string;
|
|
15
|
+
input: Messages;
|
|
16
|
+
questions: DecisionQuestion[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Carries a field location for both OpenAI errors and Jev's 422 details. */
|
|
20
|
+
export class DecisionValidationError extends Error {
|
|
21
|
+
constructor(readonly field: string, message: string) { super(`${field}: ${message}`); }
|
|
22
|
+
}
|
|
23
|
+
const fail = (field: string, message: string): never => { throw new DecisionValidationError(field, message); };
|
|
24
|
+
function record(value: unknown, field: string): Record<string, unknown> {
|
|
25
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return fail(field, "must be an object");
|
|
26
|
+
return value as Record<string, unknown>;
|
|
27
|
+
}
|
|
28
|
+
function string(value: unknown, field: string): string {
|
|
29
|
+
if (typeof value !== "string") return fail(field, "must be a string");
|
|
30
|
+
return value;
|
|
31
|
+
}
|
|
32
|
+
function list(value: unknown, field: string, minimum: number, maximum: number): unknown[] {
|
|
33
|
+
if (!Array.isArray(value) || value.length < minimum || value.length > maximum)
|
|
34
|
+
return fail(field, `must contain ${minimum}–${maximum} entries`);
|
|
35
|
+
return value;
|
|
36
|
+
}
|
|
37
|
+
function text(value: unknown, field: string, nullable = false): string {
|
|
38
|
+
if (value === null && nullable) return "";
|
|
39
|
+
if (typeof value !== "string" && (!value || typeof value !== "object")) return fail(field, "must be text, an object, or an array");
|
|
40
|
+
finiteJson(value, field);
|
|
41
|
+
return typeof value === "string" ? value : JSON.stringify(value);
|
|
42
|
+
}
|
|
43
|
+
function finiteJson(value: unknown, field: string, depth = 0): void {
|
|
44
|
+
if (depth > 64) return fail(field, "exceeds 64 nested levels");
|
|
45
|
+
if (typeof value === "number" && !Number.isFinite(value)) return fail(field, "numbers must be finite");
|
|
46
|
+
if (value && typeof value === "object") for (const child of Object.values(value)) finiteJson(child, field, depth + 1);
|
|
47
|
+
}
|
|
48
|
+
const predicateOptions = (): DecisionOption[] => [
|
|
49
|
+
{ value: false, label: "false", description: "The answer is no; the condition is false." },
|
|
50
|
+
{ value: true, label: "true", description: "The answer is yes; the condition is true." },
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
/** OpenAI Decisions request contract: https://developers.openai.com/api/reference/resources/decisions/methods/create */
|
|
54
|
+
export function parseOpenAiDecision(value: unknown): DecisionRequest {
|
|
55
|
+
const body = record(value, "body");
|
|
56
|
+
const model = string(body.model, "model");
|
|
57
|
+
let imageCount = 0;
|
|
58
|
+
const input: Messages = typeof body.input === "string" ? [{ role: "user", content: body.input }]
|
|
59
|
+
: list(body.input, "input", 1, Number.MAX_SAFE_INTEGER).map((entry, i) => {
|
|
60
|
+
const at = `input.${i}`, message = record(entry, at);
|
|
61
|
+
if (message.role !== "user" || (message.type !== undefined && message.type !== "message"))
|
|
62
|
+
return fail(at, "must be a user message");
|
|
63
|
+
const content = typeof message.content === "string" ? message.content
|
|
64
|
+
: list(message.content, `${at}.content`, 1, Number.MAX_SAFE_INTEGER).map((entry, j) => {
|
|
65
|
+
const part = record(entry, `${at}.content.${j}`);
|
|
66
|
+
if (part.type === "input_text") return { type: "text", text: string(part.text, `${at}.content.${j}.text`) };
|
|
67
|
+
if (part.type === "input_image") {
|
|
68
|
+
imageCount++;
|
|
69
|
+
if (imageCount > 128) return fail("input", "supports at most 128 images");
|
|
70
|
+
const detail = part.detail;
|
|
71
|
+
if (detail != null && !["auto", "low", "high", "original"].includes(String(detail)))
|
|
72
|
+
return fail(`${at}.content.${j}.detail`, "invalid image detail");
|
|
73
|
+
return { type: "image_url", image_url: { url: string(part.image_url, `${at}.content.${j}.image_url`), ...(detail ? { detail } : {}) } };
|
|
74
|
+
}
|
|
75
|
+
return fail(`${at}.content.${j}.type`, "expected input_text or input_image");
|
|
76
|
+
});
|
|
77
|
+
return { role: "user", content };
|
|
78
|
+
});
|
|
79
|
+
const questions = list(body.questions, "questions", 1, 200).map((entry, i): DecisionQuestion => {
|
|
80
|
+
const at = `questions.${i}`, q = record(entry, at), instructions = string(q.instructions, `${at}.instructions`);
|
|
81
|
+
const name = q.name === undefined ? null : string(q.name, `${at}.name`);
|
|
82
|
+
if (q.type === "predicate") return { type: "predicate", name, instructions, options: predicateOptions() };
|
|
83
|
+
if (q.type === "choice") {
|
|
84
|
+
const options = list(q.choices, `${at}.choices`, 2, 255).map((entry, j) => {
|
|
85
|
+
const choice = record(entry, `${at}.choices.${j}`), value = choice.value;
|
|
86
|
+
if (typeof value !== "string" && typeof value !== "boolean") return fail(`${at}.choices.${j}.value`, "must be a string or boolean");
|
|
87
|
+
return { value, label: JSON.stringify(value), description: choice.description === undefined ? "" : string(choice.description, `${at}.choices.${j}.description`) };
|
|
88
|
+
});
|
|
89
|
+
if (new Set(options.map(option => JSON.stringify(option.value))).size !== options.length)
|
|
90
|
+
return fail(`${at}.choices`, "values must be unique (strings and booleans are distinct)");
|
|
91
|
+
return { type: "choice", name, instructions, options };
|
|
92
|
+
}
|
|
93
|
+
if (q.type === "score") {
|
|
94
|
+
const options = list(q.levels, `${at}.levels`, 2, 255).map((entry, j) => {
|
|
95
|
+
const level = record(entry, `${at}.levels.${j}`);
|
|
96
|
+
return { value: j, label: string(level.label, `${at}.levels.${j}.label`),
|
|
97
|
+
description: level.description === undefined ? "" : string(level.description, `${at}.levels.${j}.description`) };
|
|
98
|
+
});
|
|
99
|
+
return { type: "score", name, instructions, options };
|
|
100
|
+
}
|
|
101
|
+
return fail(`${at}.type`, "expected predicate, choice, or score");
|
|
102
|
+
});
|
|
103
|
+
return { model, input, questions };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Jev System One contract: https://docs.typesafe.ai/api */
|
|
107
|
+
export function parseJevDecision(value: unknown): DecisionRequest {
|
|
108
|
+
const body = record(value, "body"), model = string(body.model, "model");
|
|
109
|
+
const state = text(body.state, "state");
|
|
110
|
+
const assets = new Map<string, string>(), used = new Set<string>();
|
|
111
|
+
for (const [reference, value] of Object.entries(body.assets == null ? {} : record(body.assets, "assets"))) {
|
|
112
|
+
if (!reference.trim() || Buffer.byteLength(reference) > 1024) return fail("assets", "invalid image reference");
|
|
113
|
+
const asset = record(value, `assets.${reference}`);
|
|
114
|
+
if (asset.type !== undefined && asset.type !== "image") return fail(`assets.${reference}.type`, "expected image");
|
|
115
|
+
if (!["png", "jpg", "jpeg"].includes(String(asset.format))) return fail(`assets.${reference}.format`, "expected png, jpg, or jpeg");
|
|
116
|
+
const data = string(asset.data_base64, `assets.${reference}.data_base64`);
|
|
117
|
+
if (!/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(data) || !data.length)
|
|
118
|
+
return fail(`assets.${reference}.data_base64`, "invalid base64 image");
|
|
119
|
+
assets.set(reference, `data:image/${asset.format === "png" ? "png" : "jpeg"};base64,${data}`);
|
|
120
|
+
}
|
|
121
|
+
// Only values may refer to images. IDs and option keys stay text. Matching is
|
|
122
|
+
// left-to-right, with whole-value matches taking precedence over substrings.
|
|
123
|
+
const imagesIn = (value: unknown): { reference: string; url: string }[] => {
|
|
124
|
+
if (value && typeof value === "object") return Object.values(value).flatMap(imagesIn);
|
|
125
|
+
if (typeof value !== "string") return [];
|
|
126
|
+
if (assets.has(value)) { used.add(value); return [{ reference: value, url: assets.get(value)! }]; }
|
|
127
|
+
const found: { reference: string; url: string }[] = [];
|
|
128
|
+
for (let position = 0; position < value.length;) {
|
|
129
|
+
const matches = [...assets.keys()].map(reference => ({ reference, at: value.indexOf(reference, position) }))
|
|
130
|
+
.filter(match => match.at >= 0).sort((a, b) => a.at - b.at || b.reference.length - a.reference.length);
|
|
131
|
+
const match = matches[0];
|
|
132
|
+
if (!match) break;
|
|
133
|
+
found.push({ reference: match.reference, url: assets.get(match.reference)! }); used.add(match.reference);
|
|
134
|
+
position = match.at + match.reference.length;
|
|
135
|
+
}
|
|
136
|
+
return found;
|
|
137
|
+
};
|
|
138
|
+
const stateImages = imagesIn(body.state);
|
|
139
|
+
const entries = Object.entries(record(body.questions, "questions"));
|
|
140
|
+
if (!entries.length) return fail("questions", "must contain at least one question");
|
|
141
|
+
const questions = entries.map(([name, entry]): DecisionQuestion => {
|
|
142
|
+
const at = `questions.${name}`, q = record(entry, at), instructions = text(q.instructions ?? null, `${at}.instructions`, true);
|
|
143
|
+
if (q.type === "noul") {
|
|
144
|
+
const options = predicateOptions();
|
|
145
|
+
if (q.criteria != null) {
|
|
146
|
+
const criteria = record(q.criteria, `${at}.criteria`);
|
|
147
|
+
for (const option of options) if (Object.hasOwn(criteria, String(option.value)))
|
|
148
|
+
option.description = text(criteria[String(option.value)], `${at}.criteria.${option.value}`, true);
|
|
149
|
+
}
|
|
150
|
+
return { type: "predicate", name, instructions, options, images: [...imagesIn(q.instructions), ...imagesIn(q.criteria)] };
|
|
151
|
+
}
|
|
152
|
+
if (q.type === "choice") {
|
|
153
|
+
const choices = Object.entries(record(q.criteria, `${at}.criteria`));
|
|
154
|
+
if (choices.length < 1 || choices.length > 255) return fail(`${at}.criteria`, "requires 1–255 options");
|
|
155
|
+
return { type: "choice", name, instructions, images: [...imagesIn(q.instructions), ...imagesIn(q.criteria)], options: choices.map(([value, description]) => ({
|
|
156
|
+
value, label: JSON.stringify(value), description: text(description, `${at}.criteria.${value}`, true),
|
|
157
|
+
})) };
|
|
158
|
+
}
|
|
159
|
+
if (q.type === "score") return { type: "score", name, instructions, images: [...imagesIn(q.instructions), ...imagesIn(q.criteria)],
|
|
160
|
+
options: list(q.criteria, `${at}.criteria`, 2, 10).map((entry, index) => ({
|
|
161
|
+
value: index, label: text(entry, `${at}.criteria.${index}`, true) || String(index), description: "", legend: entry,
|
|
162
|
+
})) };
|
|
163
|
+
return fail(`${at}.type`, "expected noul, choice, or score");
|
|
164
|
+
});
|
|
165
|
+
if (used.size !== assets.size) return fail("assets", "every image asset must be referenced");
|
|
166
|
+
return { model, input: [{ role: "user", content: stateImages.length ? [
|
|
167
|
+
{ type: "text", text: state }, ...imageParts(stateImages),
|
|
168
|
+
] : state }], questions };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function imageParts(images: { reference: string; url: string }[]): { type: string; [key: string]: unknown }[] {
|
|
172
|
+
return images.flatMap(image => [{ type: "text", text: `Image reference: ${image.reference}` },
|
|
173
|
+
{ type: "image_url", image_url: { url: image.url } }]);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** Exact wire values encoded as JSON keep string "true" distinct from boolean
|
|
177
|
+
* true, preserve whitespace and empty strings, and never shorten multi-token
|
|
178
|
+
* answers. Score questions use their supplied labels as the answer text. */
|
|
179
|
+
export function decisionCandidates(tokenizer: Pick<LoadedTokenizer, "encode">, question: DecisionQuestion, ending: string): number[][] {
|
|
180
|
+
return question.options.map(option => tokenizer.encode(
|
|
181
|
+
JSON.stringify(question.type === "score" ? option.label : option.value) + ending, false));
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export function decisionMessages(input: Messages, question: DecisionQuestion): Messages {
|
|
185
|
+
const menu = question.options.map(option => `${question.type === "score" ? JSON.stringify(option.label) : JSON.stringify(option.value)}${option.description ? ` — ${option.description}` : ""}`).join("\n");
|
|
186
|
+
const questionText = `Evaluate the preceding input.\nQuestion: ${question.instructions}\nAllowed answers${question.type === "score" ? " (ordered from lowest to highest)" : ""}:\n${menu}\nAnswer with exactly one of the JSON values above. Do not explain.`;
|
|
187
|
+
// Both protocols supply user input. Keep it in one turn so templates that
|
|
188
|
+
// enforce user/assistant alternation accept it, retaining media order.
|
|
189
|
+
const content = input.flatMap((message, i) => [
|
|
190
|
+
...(i ? [{ type: "text" as const, text: "\n\n" }] : []),
|
|
191
|
+
...(typeof message.content === "string" ? [{ type: "text" as const, text: message.content }] : message.content ?? []),
|
|
192
|
+
]);
|
|
193
|
+
content.push({ type: "text", text: `\n\n${questionText}` }, ...imageParts(question.images ?? []));
|
|
194
|
+
return [{ role: "user", content: content.every(part => part.type === "text")
|
|
195
|
+
? content.map(part => (part as { text: string }).text).join("") : content }];
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function winner(probabilities: readonly number[]): number {
|
|
199
|
+
return probabilities.reduce((best, p, i) => p > probabilities[best]! ? i : best, 0);
|
|
200
|
+
}
|
|
201
|
+
/** Jev's documented distribution-derived confidence, including ordered levels.
|
|
202
|
+
* OpenAI does not publish a confidence formula; the local adapter uses this
|
|
203
|
+
* same statistic. This is a statistic of the likelihoods, with no learned
|
|
204
|
+
* calibration stage. */
|
|
205
|
+
export function decisionConfidence(probabilities: readonly number[], ordered: boolean): number {
|
|
206
|
+
const n = probabilities.length, best = winner(probabilities);
|
|
207
|
+
if (n === 1) return 1;
|
|
208
|
+
if (!ordered) return Math.max(0, Math.min(1, (probabilities[best]! * n - 1) / (n - 1)));
|
|
209
|
+
const spread = probabilities.reduce((sum, p, i) => sum + p * Math.abs(i - best), 0);
|
|
210
|
+
const uniform = probabilities.reduce((sum, _, i) => sum + Math.abs(i - (n - 1) / 2) / n, 0);
|
|
211
|
+
return Math.max(0, Math.min(1, 1 - spread / uniform));
|
|
212
|
+
}
|
|
213
|
+
export function decisionAnswer(question: DecisionQuestion, probabilities: number[], jev: boolean): Record<string, unknown> {
|
|
214
|
+
const { type, name, options } = question;
|
|
215
|
+
if (type === "predicate") return jev ? { type: "noul", noul: probabilities[1] }
|
|
216
|
+
: { type, name, probability: probabilities[1] };
|
|
217
|
+
const confidence = decisionConfidence(probabilities, type === "score");
|
|
218
|
+
if (type === "choice") {
|
|
219
|
+
const choice = options[winner(probabilities)]!.value;
|
|
220
|
+
return jev ? { type, choice, confidence, probabilities: Object.fromEntries(options.map((option, i) => [option.value, probabilities[i]])) }
|
|
221
|
+
: { type, name, choice, confidence, probabilities: options.map((option, i) => ({ value: option.value, probability: probabilities[i] })) };
|
|
222
|
+
}
|
|
223
|
+
const score = probabilities.reduce((sum, p, i) => sum + p * i, 0);
|
|
224
|
+
return jev ? { type, score, confidence,
|
|
225
|
+
legend: Object.fromEntries(options.map((option, i) => [i, Object.hasOwn(option, "legend") ? option.legend : option.label])),
|
|
226
|
+
probabilities: Object.fromEntries(probabilities.map((p, i) => [i, p])) }
|
|
227
|
+
: { type, name, score, confidence, probabilities: options.map((option, i) => ({ value: i, label: option.label, probability: probabilities[i] })) };
|
|
228
|
+
}
|
|
@@ -1,14 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { Registry } from "@mlx-bun/hub/registry";
|
|
2
|
+
import { openRegistry } from "../storage/paths";
|
|
3
|
+
import type { ModelBinding } from "../engine/model-binding";
|
|
2
4
|
import pkgJson from "../../package.json" with { type: "json" };
|
|
3
|
-
import type {
|
|
4
|
-
import type { GenerationGateway } from "./generation-gateway";
|
|
5
|
-
import { fit } from "../fit";
|
|
5
|
+
import type { LoadedModelContext as ModelContext } from "../engine/model-host";
|
|
6
6
|
|
|
7
7
|
const pkgVersion = (pkgJson as { version: string }).version;
|
|
8
8
|
|
|
9
9
|
export type DiscoveryRoute =
|
|
10
|
-
| "library"
|
|
11
|
-
| "downloads"
|
|
12
10
|
| "api-index"
|
|
13
11
|
| "health"
|
|
14
12
|
| "models";
|
|
@@ -16,8 +14,6 @@ export type DiscoveryRoute =
|
|
|
16
14
|
export function matchDiscoveryRoute(method: string, pathname: string): DiscoveryRoute | null {
|
|
17
15
|
if (method !== "GET") return null;
|
|
18
16
|
switch (pathname) {
|
|
19
|
-
case "/library": return "library";
|
|
20
|
-
case "/downloads": return "downloads";
|
|
21
17
|
case "/v1": return "api-index";
|
|
22
18
|
case "/health": return "health";
|
|
23
19
|
case "/v1/models": return "models";
|
|
@@ -27,7 +23,6 @@ export function matchDiscoveryRoute(method: string, pathname: string): Discovery
|
|
|
27
23
|
|
|
28
24
|
export interface DiscoveryRoutes {
|
|
29
25
|
handle(url: URL, request: Request): Promise<Response | null>;
|
|
30
|
-
invalidateLibrary(): void;
|
|
31
26
|
}
|
|
32
27
|
|
|
33
28
|
export interface TranscriptionInfo {
|
|
@@ -36,71 +31,17 @@ export interface TranscriptionInfo {
|
|
|
36
31
|
}
|
|
37
32
|
|
|
38
33
|
export function createDiscoveryRoutes(
|
|
39
|
-
ctx:
|
|
40
|
-
|
|
34
|
+
ctx: ModelContext,
|
|
35
|
+
binding: Pick<ModelBinding, "discovery">,
|
|
41
36
|
startedAt: number,
|
|
42
37
|
transcription: () => Promise<TranscriptionInfo | null> = async () => null,
|
|
38
|
+
createRegistry: () => Pick<Registry, "scan" | "listCanonical" | "close"> = () => openRegistry(),
|
|
39
|
+
/** The server's own sampling defaults (`--temperature`, `--top-p`, `--top-k`): they win over the model's, as they do for a request that names none. */
|
|
40
|
+
serverDefaults: { temperature?: number; topP?: number; topK?: number } = {},
|
|
43
41
|
): DiscoveryRoutes {
|
|
44
|
-
let libraryCache: { at: number; rows: unknown[] } | null = null;
|
|
45
|
-
|
|
46
42
|
return {
|
|
47
|
-
invalidateLibrary() {
|
|
48
|
-
libraryCache = null;
|
|
49
|
-
},
|
|
50
|
-
|
|
51
43
|
async handle(url, request) {
|
|
52
44
|
switch (matchDiscoveryRoute(request.method, url.pathname)) {
|
|
53
|
-
case "library": {
|
|
54
|
-
if (url.searchParams.get("refresh") === "1" || !libraryCache || Date.now() - libraryCache.at > 30_000) {
|
|
55
|
-
const { Registry, visionCapable, audioCapable } = await import("../registry");
|
|
56
|
-
const { loadModelConfig } = await import("../config");
|
|
57
|
-
const { supportTier } = await import("../model/support");
|
|
58
|
-
const registry = new Registry();
|
|
59
|
-
await registry.scan();
|
|
60
|
-
const rows = [];
|
|
61
|
-
for (const model of registry.listCanonical()) {
|
|
62
|
-
const tier = supportTier(model.modelType, model.repoId);
|
|
63
|
-
const supported = tier !== null;
|
|
64
|
-
let assessment = null;
|
|
65
|
-
try {
|
|
66
|
-
const config = await loadModelConfig(model.path);
|
|
67
|
-
const result = fit(
|
|
68
|
-
config,
|
|
69
|
-
model.sizeBytes,
|
|
70
|
-
8192,
|
|
71
|
-
undefined,
|
|
72
|
-
undefined,
|
|
73
|
-
model.expertsBytes,
|
|
74
|
-
);
|
|
75
|
-
assessment = {
|
|
76
|
-
fits: result.fits,
|
|
77
|
-
max_safe_context: result.maxSafeContext,
|
|
78
|
-
predicted_decode_tps: result.predictedDecodeTps,
|
|
79
|
-
};
|
|
80
|
-
} catch {}
|
|
81
|
-
rows.push({
|
|
82
|
-
repo_id: model.repoId,
|
|
83
|
-
model_type: model.modelType,
|
|
84
|
-
size_bytes: model.sizeBytes,
|
|
85
|
-
quant_bits: model.quantBits,
|
|
86
|
-
vision: visionCapable(model),
|
|
87
|
-
audio: audioCapable(model),
|
|
88
|
-
supported,
|
|
89
|
-
support_tier: tier,
|
|
90
|
-
serving: model.repoId === ctx.modelId,
|
|
91
|
-
assessment,
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
libraryCache = { at: Date.now(), rows };
|
|
95
|
-
}
|
|
96
|
-
return Response.json({ models: libraryCache.rows });
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
case "downloads": {
|
|
100
|
-
const { downloadsSnapshot } = await import("../download");
|
|
101
|
-
return Response.json({ downloads: downloadsSnapshot() });
|
|
102
|
-
}
|
|
103
|
-
|
|
104
45
|
case "api-index":
|
|
105
46
|
return Response.json({
|
|
106
47
|
name: "mlx-bun",
|
|
@@ -112,10 +53,11 @@ export function createDiscoveryRoutes(
|
|
|
112
53
|
"POST /v1/messages",
|
|
113
54
|
"POST /v1/responses",
|
|
114
55
|
"POST /v1/embeddings",
|
|
56
|
+
"POST /v1/decisions",
|
|
57
|
+
"POST /v1/systemone",
|
|
115
58
|
"POST /v1/audio/transcriptions",
|
|
116
59
|
"POST /v1/audio/translations",
|
|
117
60
|
"GET /v1/models",
|
|
118
|
-
"GET/POST/DELETE /v1/adapters",
|
|
119
61
|
"GET /health",
|
|
120
62
|
"GET /stats",
|
|
121
63
|
"GET /fit",
|
|
@@ -135,11 +77,11 @@ export function createDiscoveryRoutes(
|
|
|
135
77
|
: null;
|
|
136
78
|
const created = Math.floor(startedAt / 1000);
|
|
137
79
|
const genDefaults = {
|
|
138
|
-
temperature: ctx.genDefaults.temperature ?? null,
|
|
139
|
-
top_p: ctx.genDefaults.topP ?? null,
|
|
140
|
-
top_k: ctx.genDefaults.topK ?? null,
|
|
80
|
+
temperature: serverDefaults.temperature ?? ctx.genDefaults.temperature ?? null,
|
|
81
|
+
top_p: serverDefaults.topP ?? ctx.genDefaults.topP ?? null,
|
|
82
|
+
top_k: serverDefaults.topK ?? ctx.genDefaults.topK ?? null,
|
|
141
83
|
};
|
|
142
|
-
const capabilities =
|
|
84
|
+
const capabilities = binding.discovery;
|
|
143
85
|
const stt = await transcription();
|
|
144
86
|
const data: Array<Record<string, unknown>> = [{
|
|
145
87
|
id: ctx.modelId,
|
|
@@ -147,19 +89,19 @@ export function createDiscoveryRoutes(
|
|
|
147
89
|
created,
|
|
148
90
|
owned_by: "mlx-bun",
|
|
149
91
|
context_window:
|
|
150
|
-
ctx.
|
|
92
|
+
ctx.memoryPlan?.contextTokens ??
|
|
151
93
|
ctx.model.config.text.maxPositionEmbeddings,
|
|
152
|
-
reasoning: ctx.template
|
|
94
|
+
reasoning: ctx.template?.supportsThinking ?? false,
|
|
153
95
|
vision: !!(ctx.vision || ctx.loadVision),
|
|
154
96
|
audio: !!(ctx.audio || ctx.loadAudio),
|
|
155
|
-
batch_mode:
|
|
97
|
+
batch_mode: "batch",
|
|
156
98
|
tools: true,
|
|
157
99
|
structured_output: true,
|
|
158
100
|
embeddings: capabilities.embeddings,
|
|
159
101
|
adapters: capabilities.adapters,
|
|
160
102
|
training: capabilities.training,
|
|
161
103
|
dsa: capabilities.dsa,
|
|
162
|
-
mtp: ctx.draft?.
|
|
104
|
+
mtp: ctx.draft?.native === true,
|
|
163
105
|
capabilities: {
|
|
164
106
|
chat_completions: true,
|
|
165
107
|
text_completions: true,
|
|
@@ -170,6 +112,7 @@ export function createDiscoveryRoutes(
|
|
|
170
112
|
structured_output: true,
|
|
171
113
|
logprobs: true,
|
|
172
114
|
embeddings: capabilities.embeddings,
|
|
115
|
+
decisions: binding.discovery.decisions ?? false,
|
|
173
116
|
vision: !!(ctx.vision || ctx.loadVision),
|
|
174
117
|
audio: !!(ctx.audio || ctx.loadAudio),
|
|
175
118
|
adapters: capabilities.adapters,
|
|
@@ -185,14 +128,14 @@ export function createDiscoveryRoutes(
|
|
|
185
128
|
capabilities: { transcription: true, translation: true, chat_completions: false },
|
|
186
129
|
});
|
|
187
130
|
try {
|
|
188
|
-
const {
|
|
189
|
-
const {
|
|
190
|
-
const registry =
|
|
131
|
+
const { visionCapable } = await import("@mlx-bun/hub/registry");
|
|
132
|
+
const { listedSupportTier } = await import("@mlx-bun/inference/models/support");
|
|
133
|
+
const registry = createRegistry();
|
|
191
134
|
try {
|
|
192
|
-
if (registry.
|
|
135
|
+
if (registry.listCanonical().length === 0) await registry.scan();
|
|
193
136
|
for (const model of registry.listCanonical()) {
|
|
194
137
|
if (model.repoId === ctx.modelId || model.repoId === stt?.id) continue;
|
|
195
|
-
const tier =
|
|
138
|
+
const tier = listedSupportTier(model);
|
|
196
139
|
if (tier === null) continue;
|
|
197
140
|
data.push({
|
|
198
141
|
id: model.repoId,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { AdmissionRejected } from "
|
|
1
|
+
import { AdmissionRejected } from "@mlx-bun/inference/execution/admission";
|
|
2
|
+
import { UnsupportedExecutionError } from "../engine/completion";
|
|
2
3
|
// The HTTP end of the pipeline: build + admit a request (errors → a JSON
|
|
3
4
|
// error response in the surface's own shape), then run it and write the
|
|
4
5
|
// result as JSON, or as protocol frames while events arrive. One writer for
|
|
@@ -11,7 +12,7 @@ import type {
|
|
|
11
12
|
} from "./inference-request";
|
|
12
13
|
import { openAiSummaryUsage, openAiUsage } from "./openai-wire";
|
|
13
14
|
import { RequestError } from "./pipeline";
|
|
14
|
-
import type { PromptResponseTrace } from "
|
|
15
|
+
import type { PromptResponseTrace } from "@mlx-bun/inference/runtime/trace";
|
|
15
16
|
|
|
16
17
|
export type ErrorFormatter = (status: number, message: string, body: Record<string, unknown>) => Response;
|
|
17
18
|
|
|
@@ -19,13 +20,21 @@ export type ErrorFormatter = (status: number, message: string, body: Record<stri
|
|
|
19
20
|
export const openAiError: ErrorFormatter = (status, _message, body) =>
|
|
20
21
|
Response.json({ error: body }, { status });
|
|
21
22
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
function errorMessage(error: unknown): string {
|
|
24
|
+
try { return error instanceof Error ? error.message : String(error); }
|
|
25
|
+
catch { return "Unknown error"; }
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Request cancellation and capability gaps are expected responses, not 500s. */
|
|
29
|
+
export function errorResponse(e: unknown, context: string, format: ErrorFormatter = openAiError, signal?: AbortSignal): Response {
|
|
30
|
+
if (signal?.aborted) return format(499, "request cancelled", { message: "request cancelled", type: "request_cancelled", code: "request_cancelled" });
|
|
31
|
+
if (e instanceof UnsupportedExecutionError) return format(501, e.message, {
|
|
32
|
+
message: e.message, type: "not_implemented", code: "unsupported_execution", reasons: e.reasons,
|
|
33
|
+
});
|
|
25
34
|
if (e instanceof AdmissionRejected) return format(429, e.message, { message: e.message, type: "resource_admission", code: "queue_full" });
|
|
26
35
|
if (e instanceof RequestError) return format(e.status, e.message, e.body);
|
|
27
|
-
|
|
28
|
-
|
|
36
|
+
const message = errorMessage(e);
|
|
37
|
+
console.error(`[serve] 500 on ${context}:\n${e instanceof Error ? e.stack ?? message : message}`);
|
|
29
38
|
return format(500, message, { message });
|
|
30
39
|
}
|
|
31
40
|
|
|
@@ -38,6 +47,7 @@ export async function admit(
|
|
|
38
47
|
trace: PromptResponseTrace | undefined,
|
|
39
48
|
context: string,
|
|
40
49
|
format: ErrorFormatter = openAiError,
|
|
50
|
+
signal?: AbortSignal,
|
|
41
51
|
): Promise<{ admitted: AdmittedRequest } | { response: Response }> {
|
|
42
52
|
const closePrepare = trace?.begin("request.prompt_prepare");
|
|
43
53
|
try {
|
|
@@ -46,8 +56,8 @@ export async function admit(
|
|
|
46
56
|
return { admitted };
|
|
47
57
|
} catch (e) {
|
|
48
58
|
closePrepare?.();
|
|
49
|
-
trace?.finish("error", { stage: "prepare_completion" });
|
|
50
|
-
return { response: errorResponse(e, context, format) };
|
|
59
|
+
trace?.finish(signal?.aborted ? "abort" : "error", { stage: "prepare_completion" });
|
|
60
|
+
return { response: errorResponse(e, context, format, signal) };
|
|
51
61
|
}
|
|
52
62
|
}
|
|
53
63
|
|
|
@@ -65,13 +75,14 @@ export async function respondJson(
|
|
|
65
75
|
): Promise<Response> {
|
|
66
76
|
try {
|
|
67
77
|
const result = await stage.run(admitted, { signal, ...(trace ? { trace } : {}) });
|
|
78
|
+
signal.throwIfAborted();
|
|
68
79
|
trace?.mark("response.final_write");
|
|
69
80
|
const response = Response.json(json(result), { headers: warningHeaders(admitted) });
|
|
70
81
|
trace?.finish("success");
|
|
71
82
|
return response;
|
|
72
83
|
} catch (e) {
|
|
73
84
|
trace?.finish(signal.aborted ? "abort" : "error");
|
|
74
|
-
return errorResponse(e, `request ${admitted.requestId}`, format);
|
|
85
|
+
return errorResponse(e, `request ${admitted.requestId}`, format, signal);
|
|
75
86
|
}
|
|
76
87
|
}
|
|
77
88
|
|
|
@@ -107,8 +118,8 @@ export function respondStream(
|
|
|
107
118
|
// ignored by clients and do not alter the completion event stream.
|
|
108
119
|
const heartbeat = setInterval(() => emit([": keep-alive\n\n"]), heartbeatMs);
|
|
109
120
|
try {
|
|
110
|
-
// The gateway owns
|
|
111
|
-
// runs per-request (concurrently
|
|
121
|
+
// The gateway owns execution placement + GPU exclusivity; this body
|
|
122
|
+
// runs per-request (concurrently, each writing
|
|
112
123
|
// its own stream — the per-row fan-out).
|
|
113
124
|
emit(protocol.start());
|
|
114
125
|
const result = await stage.run(admitted, {
|
|
@@ -123,13 +134,14 @@ export function respondStream(
|
|
|
123
134
|
},
|
|
124
135
|
onUsageProgress: (usage) => { latestUsage = usage; },
|
|
125
136
|
});
|
|
137
|
+
generationSignal.throwIfAborted();
|
|
126
138
|
emit(protocol.finish(result.finishReason, openAiSummaryUsage(result)));
|
|
127
139
|
trace?.mark("response.final_write");
|
|
128
140
|
} catch (e) {
|
|
129
141
|
outcome = generationSignal.aborted ? "abort" : "error";
|
|
130
142
|
if (!generationSignal.aborted) {
|
|
131
143
|
emit([
|
|
132
|
-
...protocol.error((e
|
|
144
|
+
...protocol.error(errorMessage(e)),
|
|
133
145
|
...protocol.finish("stop", latestUsage ? openAiUsage(latestUsage) : {}),
|
|
134
146
|
]);
|
|
135
147
|
}
|