mlx-bun 0.2.0 → 0.3.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.
Files changed (364) hide show
  1. package/README.md +71 -588
  2. package/docs/README.md +91 -0
  3. package/docs/{design → archive/investigations}/audio-input-plan.md +4 -4
  4. package/docs/{design → archive/investigations}/diffusion-gemma-port.md +3 -3
  5. package/docs/{design → archive/investigations}/faithful-l1-consolidation.md +4 -4
  6. package/docs/{design → archive/investigations}/minicpm5-decode-megakernel.md +11 -11
  7. package/docs/{design → archive/investigations}/optimization_plan.md +1 -1
  8. package/docs/{design → archive/investigations}/pre-colibri-stabilization.md +5 -5
  9. package/docs/{design → archive/investigations}/the-dreaming-handoff.md +2 -2
  10. package/docs/design/batching.md +569 -0
  11. package/docs/design/decode-speed-program.md +3178 -169
  12. package/docs/design/dreaming-nightly-pipeline.md +675 -282
  13. package/docs/design/generic-model-support.md +579 -334
  14. package/docs/design/kv-cache.md +680 -0
  15. package/docs/design/orpo-training.md +673 -1239
  16. package/docs/design/speculative-decoding.md +1627 -0
  17. package/docs/design/turboquant.md +2102 -0
  18. package/docs/design/unified-engine-frontier-plan.md +1831 -727
  19. package/docs/design/web-chat-redesign.md +666 -1083
  20. package/docs/planning/PRODUCT_ROADMAP.md +34 -1
  21. package/docs/planning/release-notes-v0.3.0.md +35 -0
  22. package/docs/planning/video-series.md +2 -2
  23. package/docs/reference/benchmarks.md +1162 -0
  24. package/docs/reference/cli.md +394 -159
  25. package/docs/reference/distribution.md +321 -182
  26. package/docs/reference/environment.md +348 -0
  27. package/docs/reference/glossary.md +39 -0
  28. package/docs/reference/library-api.md +566 -168
  29. package/docs/reference/memory.md +227 -64
  30. package/docs/reference/models.md +104 -6
  31. package/docs/reference/server-api.md +886 -425
  32. package/docs/reference/server-config.md +638 -330
  33. package/docs/reference/training.md +393 -254
  34. package/docs/reference/troubleshooting.md +166 -0
  35. package/package.json +9 -4
  36. package/src/audio/decode.ts +2 -2
  37. package/src/audio/features.ts +2 -2
  38. package/src/backends/mlx/autoregressive.ts +108 -0
  39. package/src/backends/mlx/batch-group.ts +1646 -0
  40. package/src/backends/mlx/diffusion.ts +39 -0
  41. package/src/backends/mlx/draft-target.ts +47 -0
  42. package/src/backends/mlx/evaluation.ts +105 -0
  43. package/src/backends/mlx/fill-append.ts +45 -0
  44. package/src/backends/mlx/gateway-binding.ts +71 -0
  45. package/src/backends/mlx/graph.ts +38 -0
  46. package/src/backends/mlx/memory-client.ts +189 -0
  47. package/src/backends/mlx/memory-guard.ts +40 -0
  48. package/src/backends/mlx/methods.ts +90 -0
  49. package/src/backends/mlx/model-prompt.ts +164 -0
  50. package/src/backends/mlx/model-serving.ts +79 -0
  51. package/src/backends/mlx/prefill.ts +33 -0
  52. package/src/backends/mlx/request-policy.ts +20 -0
  53. package/src/backends/mlx/rollback.ts +51 -0
  54. package/src/backends/mlx/serial-executor.ts +273 -0
  55. package/src/backends/mlx/speculative.ts +114 -0
  56. package/src/backends/mlx/state-views.ts +26 -0
  57. package/src/backends/mlx/text-engine.ts +73 -0
  58. package/src/cli.ts +58 -25
  59. package/src/client.ts +66 -0
  60. package/src/config.ts +21 -4
  61. package/src/contracts/completion.ts +16 -0
  62. package/src/contracts/execution.ts +49 -0
  63. package/src/contracts/generation.ts +103 -0
  64. package/src/contracts/host.ts +7 -0
  65. package/src/contracts/jobs.ts +66 -0
  66. package/src/contracts/pi-provider.ts +30 -0
  67. package/src/contracts/pi.ts +259 -0
  68. package/src/contracts/resources.ts +18 -0
  69. package/src/contracts/scheduling.ts +27 -0
  70. package/src/contracts/tsconfig.json +14 -0
  71. package/src/diffusion/diffusion-generate.ts +218 -153
  72. package/src/embed.ts +1 -1
  73. package/src/engine/admission.ts +59 -0
  74. package/src/engine/cancellation.ts +38 -0
  75. package/src/engine/engine.ts +56 -0
  76. package/src/engine/execution-coordinator.ts +70 -0
  77. package/src/engine/execution-plan.ts +51 -0
  78. package/src/engine/resources.ts +42 -0
  79. package/src/engine/scheduler.ts +67 -0
  80. package/src/engine/session.ts +247 -0
  81. package/src/eval/README.md +44 -1
  82. package/src/eval/kl.ts +236 -3
  83. package/src/eval/reference-logits.ts +207 -0
  84. package/src/eval/runner.ts +8 -96
  85. package/src/fill/echo-index.ts +188 -0
  86. package/src/fill/fill-session.ts +452 -0
  87. package/src/fill/proposal.ts +68 -0
  88. package/src/fill/schema-rows.ts +506 -0
  89. package/src/fill/tool-boundary.ts +103 -0
  90. package/src/fit.ts +1 -1
  91. package/src/generate.ts +656 -268
  92. package/src/grammar.ts +12 -7
  93. package/src/harness-pi.ts +14 -33
  94. package/src/hub-rest.ts +4 -4
  95. package/src/index.ts +7 -0
  96. package/src/inference/denoising.ts +17 -0
  97. package/src/inference/graph.ts +33 -0
  98. package/src/inference/prefill.ts +39 -0
  99. package/src/inference/rollback.ts +7 -0
  100. package/src/jobs/db.ts +4 -0
  101. package/src/jobs/index.ts +2 -0
  102. package/src/jobs/job-entry.ts +12 -12
  103. package/src/jobs/runner.ts +98 -13
  104. package/src/jobs/task-client.ts +19 -0
  105. package/src/jobs/types.ts +2 -58
  106. package/src/kv-store.ts +109 -49
  107. package/src/lab/README.md +18 -0
  108. package/src/{assets → lab/curve}/curve-designer.html +2 -2
  109. package/src/{curve-sampler.ts → lab/curve/curve-sampler.ts} +2 -2
  110. package/src/{expert-trace.ts → lab/expert-trace/expert-trace.ts} +5 -5
  111. package/src/{model → lab/paged-kv}/paged-kv.ts +9 -6
  112. package/src/library.ts +40 -0
  113. package/src/lora.ts +18 -0
  114. package/src/media-fetch.ts +1 -1
  115. package/src/memory/article.ts +1 -1
  116. package/src/memory/chunk.ts +1 -1
  117. package/src/memory/model.ts +30 -169
  118. package/src/memory/prompts.ts +1 -1
  119. package/src/memory/rest.ts +1 -1
  120. package/src/memory/schedule.ts +1 -1
  121. package/src/memory/stages.ts +4 -1
  122. package/src/memory/vault.ts +1 -3
  123. package/src/mlx/array.ts +9 -10
  124. package/src/mlx/compile.ts +12 -2
  125. package/src/mlx/ffi.ts +58 -11
  126. package/src/mlx/materialize.ts +23 -0
  127. package/src/mlx/metal-kernel.ts +10 -11
  128. package/src/mlx/ops.ts +33 -13
  129. package/src/mlx/quantized-rows.ts +36 -0
  130. package/src/mlx/token-bitmask.ts +52 -0
  131. package/src/mlx/turboquant-kv-decode.ts +82 -0
  132. package/src/mlx/turboquant-kv-inverse.ts +122 -0
  133. package/src/mlx/turboquant-ops.ts +33 -3
  134. package/src/mlx/turboquant-tables.ts +2 -2
  135. package/src/model/batched-mask.ts +8 -2
  136. package/src/model/batched-quant.ts +1 -1
  137. package/src/model/batched-rotating-quant.ts +2 -2
  138. package/src/model/batched-rotating.ts +1 -1
  139. package/src/model/compiled-decode.ts +1 -1
  140. package/src/model/diffusion-gemma.ts +8 -8
  141. package/src/model/factory.ts +76 -33
  142. package/src/model/fingerprint.ts +1 -1
  143. package/src/model/flash-attention.ts +3 -3
  144. package/src/model/gemma4-base.ts +26 -9
  145. package/src/model/gemma4.ts +6 -6
  146. package/src/model/generated/gemma4-12b.ts +2 -2
  147. package/src/model/generated/gemma4-26b.ts +2 -2
  148. package/src/model/generated/gemma4-e4b.ts +2 -2
  149. package/src/model/glm52-dsa.ts +13 -9
  150. package/src/model/glm52-memory.ts +1 -1
  151. package/src/model/implementation.ts +69 -0
  152. package/src/model/minicpm5.ts +6 -8
  153. package/src/model/profile.ts +12 -2
  154. package/src/model/qwen-append.ts +12 -0
  155. package/src/model/qwen-conv.ts +52 -0
  156. package/src/model/qwen3_5-checkpoint.ts +164 -0
  157. package/src/model/qwen3_5.ts +140 -56
  158. package/src/model/support.ts +1 -1
  159. package/src/model/trellis-balanced-scatter.ts +51 -0
  160. package/src/model/trellis-linear.ts +672 -0
  161. package/src/model/trellis-shared-m.ts +151 -0
  162. package/src/model/trellis-shared-scatter.ts +144 -0
  163. package/src/model/trellis-splitk-prefill.ts +158 -0
  164. package/src/model/trellis-tiled-prefill.ts +136 -0
  165. package/src/model/trellis-vector-expand.ts +62 -0
  166. package/src/model/trellis-wide-prefill.ts +94 -0
  167. package/src/model/universal/dense.ts +1 -1
  168. package/src/model/universal/modules.ts +8 -1
  169. package/src/native-pack.ts +6 -5
  170. package/src/pi-provider.ts +6 -64
  171. package/src/pi-web.ts +8 -315
  172. package/src/prompt-cache.ts +96 -53
  173. package/src/quantize/allocator.ts +1 -1
  174. package/src/quantize/config-writer.ts +10 -3
  175. package/src/quantize/rotate.ts +4 -4
  176. package/src/quantize/trellis.ts +586 -0
  177. package/src/responses.ts +28 -1
  178. package/src/runtime-config.ts +18 -4
  179. package/src/sampler.ts +4 -4
  180. package/src/serve/batch-scheduler.ts +8 -1671
  181. package/src/serve/chat-request.ts +391 -0
  182. package/src/serve/chat-stage.ts +195 -0
  183. package/src/serve/checkpoint-identity.ts +64 -0
  184. package/src/serve/completion-executor.ts +15 -7
  185. package/src/serve/discovery-routes.ts +12 -13
  186. package/src/serve/generation-gateway.ts +154 -193
  187. package/src/serve/http.ts +159 -0
  188. package/src/serve/inference-request.ts +79 -0
  189. package/src/serve/isolate.ts +297 -105
  190. package/src/serve/lab-routes.ts +3 -0
  191. package/src/serve/lane-registry.ts +2 -1
  192. package/src/serve/model-admin-routes.ts +7 -5
  193. package/src/serve/model-binding.ts +43 -0
  194. package/src/serve/model-host.ts +512 -0
  195. package/src/serve/openai-wire.ts +103 -0
  196. package/src/serve/parent-application.ts +56 -0
  197. package/src/serve/pipeline.ts +33 -0
  198. package/src/serve/preparation.ts +33 -0
  199. package/src/serve/request-plan.ts +19 -15
  200. package/src/serve/request-prep.ts +346 -0
  201. package/src/serve/response-lease.ts +24 -0
  202. package/src/serve/responses-client.ts +69 -0
  203. package/src/serve/session-completion-engine.ts +110 -0
  204. package/src/serve/session-search.ts +1 -1
  205. package/src/serve/static-routes.ts +5 -5
  206. package/src/serve/text-completion-stage.ts +104 -0
  207. package/src/serve/token-streams.ts +519 -0
  208. package/src/server.ts +317 -2649
  209. package/src/spec/assistant-source.ts +10 -44
  210. package/src/spec/deepspec-source.ts +6 -6
  211. package/src/spec/dflash-source.ts +8 -8
  212. package/src/spec/dspark/ab-stats.ts +3 -3
  213. package/src/spec/dspark/calibration.ts +2 -2
  214. package/src/spec/dspark/deepspec-module.ts +2 -2
  215. package/src/spec/dspark/generate-dflash.ts +2 -2
  216. package/src/spec/dspark/generate.ts +2 -2
  217. package/src/spec/dspark/loader.ts +1 -1
  218. package/src/spec/dspark/module-dflash.ts +4 -3
  219. package/src/spec/dspark/module.ts +3 -3
  220. package/src/spec/dspark/quantize-drafter.ts +1 -1
  221. package/src/spec/generate.ts +1 -1
  222. package/src/spec/glm52-mtp-source.ts +1 -1
  223. package/src/spec/ngram-source.ts +1 -1
  224. package/src/spec/prefix-state.ts +45 -0
  225. package/src/spec/qwen-mtp-source.ts +166 -69
  226. package/src/spec/serve-loop.ts +154 -156
  227. package/src/spec/source.ts +42 -10
  228. package/src/spec/two-model.ts +1 -1
  229. package/src/ssd-cache.ts +80 -12
  230. package/src/ssd-durability.ts +25 -13
  231. package/src/tool-call.ts +51 -14
  232. package/src/train/flash-cce.ts +2 -2
  233. package/src/train/forward.ts +6 -0
  234. package/src/train/loss.ts +1 -1
  235. package/src/train/prefix-shared.ts +5 -1
  236. package/src/train/segmented.ts +5 -3
  237. package/src/train/trainer.ts +6 -6
  238. package/src/vision/prompt.ts +1 -1
  239. package/src/vision/qwen3vl-tower.ts +52 -10
  240. package/src/web/app.html +1 -1
  241. package/src/web/app.js +5 -3
  242. package/src/web/src/adapters-panel.ts +3 -3
  243. package/src/web/src/assistant.ts +2 -36
  244. package/src/web/src/chat.ts +4 -4
  245. package/src/web/src/composer.ts +4 -4
  246. package/src/web/src/hub.ts +1 -1
  247. package/src/web/src/markdown.ts +0 -0
  248. package/src/web/src/memory-panel.ts +1 -1
  249. package/src/web/src/model-picker.ts +1 -1
  250. package/src/web/src/palette.ts +1 -1
  251. package/src/web/src/protocol.ts +3 -39
  252. package/src/web/src/sessions.ts +1 -1
  253. package/src/web/src/ui-catalog.ts +4 -18
  254. package/src/web-assets.ts +47 -0
  255. package/src/weights.ts +55 -9
  256. package/docs/archive/investigations/curve-runs/bisector-route.json +0 -98
  257. package/docs/archive/investigations/curve-runs/bisector-trace.json +0 -1393
  258. package/docs/archive/investigations/curve-runs/curve-terrain.json +0 -1179
  259. package/docs/archive/investigations/curve-runs/curve-terrain.svg +0 -42
  260. package/docs/archive/investigations/hlg-ab.html +0 -34
  261. package/docs/archive/investigations/hlg-explorer.html +0 -238
  262. package/docs/archive/investigations/hlg-figs/cliff.svg +0 -1
  263. package/docs/archive/investigations/hlg-figs/cross.svg +0 -1
  264. package/docs/archive/investigations/hlg-figs/edges.svg +0 -1
  265. package/docs/archive/investigations/hlg-figs/k20-grids.svg +0 -1
  266. package/docs/archive/investigations/hlg-figs/k20-peaks.svg +0 -1
  267. package/docs/archive/investigations/hlg-figs/lw-curve.svg +0 -1
  268. package/docs/archive/investigations/hlg-figs/terrain.svg +0 -1
  269. package/docs/archive/investigations/hlg-report.html +0 -262
  270. package/docs/archive/investigations/hlg-runs/hlg-ab.json +0 -149
  271. package/docs/archive/investigations/hlg-runs/hlg-blind-prompts.md +0 -87
  272. package/docs/archive/investigations/hlg-runs/hlg-blind.json +0 -107
  273. package/docs/archive/investigations/hlg-runs/hlg-cliff.json +0 -197
  274. package/docs/archive/investigations/hlg-runs/hlg-cross.json +0 -505
  275. package/docs/archive/investigations/hlg-runs/hlg-edge.json +0 -388
  276. package/docs/archive/investigations/hlg-runs/hlg-eval1.json +0 -25
  277. package/docs/archive/investigations/hlg-runs/hlg-frontier.json +0 -86
  278. package/docs/archive/investigations/hlg-runs/hlg-grid.json +0 -242
  279. package/docs/archive/investigations/hlg-runs/hlg-grid2.json +0 -343
  280. package/docs/archive/investigations/hlg-runs/hlg-lw-coarse.json +0 -110
  281. package/docs/archive/investigations/hlg-runs/hlg-map.json +0 -383
  282. package/docs/archive/investigations/hlg-runs/run0-original.log +0 -159
  283. package/docs/archive/investigations/hlg-runs/runA-recommended.log +0 -131
  284. package/docs/archive/investigations/hlg-runs/runAA-cliff-cpm5.log +0 -26
  285. package/docs/archive/investigations/hlg-runs/runAB-grid2-e4b.log +0 -18
  286. package/docs/archive/investigations/hlg-runs/runAC-peak-e4b-partial.log +0 -25
  287. package/docs/archive/investigations/hlg-runs/runAD-ab-samples.log +0 -8
  288. package/docs/archive/investigations/hlg-runs/runAE-edge-e4b.log +0 -51
  289. package/docs/archive/investigations/hlg-runs/runAF-logit-calibration.log +0 -10
  290. package/docs/archive/investigations/hlg-runs/runAG-eval-config2.log +0 -16
  291. package/docs/archive/investigations/hlg-runs/runB-gain-variants.log +0 -192
  292. package/docs/archive/investigations/hlg-runs/runC-pivots.log +0 -162
  293. package/docs/archive/investigations/hlg-runs/runD-oetf.log +0 -132
  294. package/docs/archive/investigations/hlg-runs/runE-eotf.log +0 -162
  295. package/docs/archive/investigations/hlg-runs/runF-shaper-working.log +0 -86
  296. package/docs/archive/investigations/hlg-runs/runG-shaper-sweep.log +0 -101
  297. package/docs/archive/investigations/hlg-runs/runH-wsweep.log +0 -176
  298. package/docs/archive/investigations/hlg-runs/runI-asweep.log +0 -176
  299. package/docs/archive/investigations/hlg-runs/runJ-knobmap.log +0 -49
  300. package/docs/archive/investigations/hlg-runs/runK-frontier.log +0 -19
  301. package/docs/archive/investigations/hlg-runs/runL-widemap.log +0 -65
  302. package/docs/archive/investigations/hlg-runs/runM-frontier-emb.log +0 -15
  303. package/docs/archive/investigations/hlg-runs/runN-verify.log +0 -116
  304. package/docs/archive/investigations/hlg-runs/runO-frontier-seed5000.log +0 -15
  305. package/docs/archive/investigations/hlg-runs/runP-grid-AxSm.log +0 -15
  306. package/docs/archive/investigations/hlg-runs/runQ-grid-tgW.log +0 -15
  307. package/docs/archive/investigations/hlg-runs/runR-blind-transcript.log +0 -79
  308. package/docs/archive/investigations/hlg-runs/runS-cross-coherence.log +0 -33
  309. package/docs/archive/investigations/hlg-runs/runT-cross-cpm5.log +0 -33
  310. package/docs/archive/investigations/hlg-runs/runU-cross-12b.log +0 -33
  311. package/docs/archive/investigations/hlg-runs/runV-lw-e4b.log +0 -22
  312. package/docs/archive/investigations/hlg-runs/runW-lw-12b.log +0 -22
  313. package/docs/archive/investigations/hlg-runs/runX-lw-cpm5.log +0 -22
  314. package/docs/archive/investigations/hlg-runs/runY-cliff-e4b.log +0 -26
  315. package/docs/archive/investigations/hlg-runs/runZ-cliff-12b.log +0 -26
  316. package/docs/archive/mlx-bun-lab-report.html +0 -275
  317. package/docs/archive/wwdc-mlx-bun.html +0 -848
  318. package/docs/design/adapters-end-to-end.md +0 -205
  319. package/docs/design/batching-perf-path.md +0 -255
  320. package/docs/design/batching-v2-plan.md +0 -529
  321. package/docs/design/bucketing-stage.md +0 -197
  322. package/docs/design/compat-cli-surface-design.md +0 -230
  323. package/docs/design/docs-reorg-plan.md +0 -165
  324. package/docs/design/dspark-serving-program.md +0 -371
  325. package/docs/design/dspark-speculative-decoding.md +0 -318
  326. package/docs/design/grammar-spec-batching-integration.md +0 -301
  327. package/docs/design/memory-inference-path.md +0 -422
  328. package/docs/design/memory-synthesis.md +0 -287
  329. package/docs/design/memory-system.md +0 -237
  330. package/docs/design/mlx-lm-serving-execution-seam.md +0 -81
  331. package/docs/design/mlx-lm-tool-parity-plan.md +0 -545
  332. package/docs/design/omlx-adoption-map.md +0 -98
  333. package/docs/design/orpo-dynamic-lambda.md +0 -220
  334. package/docs/design/orpo-future-enhancements.md +0 -340
  335. package/docs/design/paged-kv-cache.md +0 -214
  336. package/docs/design/parallel-slots.md +0 -567
  337. package/docs/design/parity-tier-dag.md +0 -161
  338. package/docs/design/repo-cleanup-plan.md +0 -231
  339. package/docs/design/runtime-isolation.md +0 -263
  340. package/docs/design/segmented-backward-training.md +0 -543
  341. package/docs/design/spec-decode-larger-targets.md +0 -229
  342. package/docs/design/ssd-kv-cold-tier.md +0 -415
  343. package/docs/design/structured-output.md +0 -484
  344. package/docs/design/tauri-desktop-app.md +0 -127
  345. package/docs/design/the-dreaming-master-plan.md +0 -445
  346. package/docs/design/turboquant-kv.md +0 -235
  347. package/docs/design/turboquant-weights.md +0 -467
  348. package/docs/design/w4a16-compute-precision-spike.md +0 -232
  349. package/docs/design/web-chat-beat-matrix.md +0 -315
  350. package/docs/design/write-pipeline-entity.md +0 -468
  351. package/docs/planning/IDEAS.md +0 -69
  352. package/docs/reference/embedding.md +0 -197
  353. package/docs/reference/features-matrix.md +0 -101
  354. package/docs/reference/orpo-quickstart.md +0 -135
  355. package/src/flags.ts +0 -11
  356. /package/docs/{design → archive/investigations}/colibri-glm52-port.md +0 -0
  357. /package/docs/{planning → archive/planning}/chunk-task-roadmap.md +0 -0
  358. /package/docs/{planning → archive/planning}/curve-sampler-research-plan.md +0 -0
  359. /package/docs/{planning → archive/planning}/memory-docs-and-dag-plan.md +0 -0
  360. /package/docs/{planning → archive/planning}/pi-integration-review.md +0 -0
  361. /package/docs/{planning → archive/planning}/release-notes-v0.2.0.md +0 -0
  362. /package/src/{memory-plan.ts → admission-plan.ts} +0 -0
  363. /package/{docs/archive/investigations → src/serve/assets}/curve-terrain.html +0 -0
  364. /package/{docs/dag → src/serve/assets}/training-inference-map.html +0 -0
@@ -0,0 +1,94 @@
1
+ // Packed Trellis prefill with MLX 0.32.2 GemvWide arithmetic.
2
+ // Ported from ml-explore/mlx v0.32.2, mlx/backend/metal/kernels/gemv.h
3
+ // (MIT). Decode weights to bf16 inside the native dot/unroll/shuffle order;
4
+ // share each decoded row across every input vector in this narrow regime.
5
+ import { MetalKernel } from "../mlx/metal-kernel";
6
+ import { type MlxArray } from "../mlx/array";
7
+ import { Dtype, deviceArchitecture } from "../mlx/ffi";
8
+ import { type TrellisGeometry } from "./trellis-linear";
9
+
10
+ /** Matches the bundled runtime's native reduction on M3 and newer GPUs. */
11
+ export function nativeTrellisWidePrefill(m: number): boolean {
12
+ return m >= 5 && m <= 15 &&
13
+ Number(/^applegpu_[a-z](\d{2})/.exec(deviceArchitecture())?.[1] ?? 0) >= 15;
14
+ }
15
+
16
+ export function wideTrellisPrefillEligible(g: TrellisGeometry, m: number, dtype: Dtype): boolean {
17
+ return dtype === Dtype.bfloat16 && nativeTrellisWidePrefill(m) &&
18
+ g.axis === 1 && g.T === 256 && g.L === 12 && [2, 3, 4].includes(g.k) &&
19
+ g.inFeatures === 5120 && g.outFeatures === 17408 && !g.blockInterleave;
20
+ }
21
+
22
+ let kernel: MetalKernel | undefined;
23
+ function wideKernel() {
24
+ return kernel ??= new MetalKernel({ name: 'mlx_bun_trellis_wide_prefill',
25
+ inputNames: ['x', 'codes', 'scales'], outputNames: ['out'], source: String.raw`
26
+ constexpr uint unroll=8, rows_per_simd=32/KL, simdgroups=KL/8;
27
+ const uint lane=thread_index_in_simdgroup, sg=simdgroup_index_in_threadgroup;
28
+ const uint kl=lane%KL, row=threadgroup_position_in_grid.y*4+sg*rows_per_simd+lane/KL;
29
+ const uint vec0=threadgroup_position_in_grid.x*SM;
30
+ constexpr uint wpb=BT*BITS/32, row_words=KC*BITS/32;
31
+ const float scale=float(scales[row]);
32
+ constexpr uint n_v4=KC/4, n_main=n_v4-n_v4%(KL*unroll);
33
+ float result[SM]={0};
34
+ for(uint base=0;base<n_main;base+=KL*unroll) {
35
+ float4 wf[unroll];
36
+ #pragma clang loop unroll(full)
37
+ for(uint i=0;i<unroll;i++) {
38
+ #pragma clang loop unroll(full)
39
+ for(uint c=0;c<4;c++) {
40
+ const uint k=(base+i*KL+kl)*4+c, block=k/BT, t=k%BT;
41
+ const device uint* bw=codes+(ulong)row*row_words+block*wpb;
42
+ const uint pos=(BT-1-t)*BITS, wi=pos>>5, off=pos&31u;
43
+ uint win=bw[wi]>>off;
44
+ if(off+L>32)win|=bw[wi+1==wpb?0:wi+1]<<(32-off);
45
+ win&=(1u<<L)-1;
46
+ const uint z=win*34038481u+76625530u;
47
+ const uint p=(z&0x00FF00FFu)+((z>>8)&0x00FF00FFu);
48
+ const int code=int((p&0xFFFFu)+(p>>16))-510;
49
+ const float reciprocal=1.0f/147.800537109375f;
50
+ const float approx=float(code)*reciprocal;
51
+ const float residual=metal::fma(-approx,147.800537109375f,float(code));
52
+ const float value=metal::fma(residual,reciprocal,approx);
53
+ wf[i][c]=float(T(value*scale));
54
+ }
55
+ }
56
+ #pragma clang loop unroll(full)
57
+ for(uint v=0;v<SM;v++) {
58
+ const uint m=min(vec0+v,uint(M-1));
59
+ float acc=0;
60
+ #pragma clang loop unroll(full)
61
+ for(uint i=0;i<unroll;i++) {
62
+ const ulong offset=(ulong)m*KC+(base+i*KL+kl)*4;
63
+ const float4 xv=float4(float(x[offset]),float(x[offset+1]),float(x[offset+2]),float(x[offset+3]));
64
+ acc+=metal::dot(wf[i],xv);
65
+ }
66
+ result[v]+=acc;
67
+ }
68
+ }
69
+ #pragma clang loop unroll(full)
70
+ for(uint v=0;v<SM;v++) {
71
+ #pragma clang loop unroll(full)
72
+ for(ushort off=KL/2;off>=1;off>>=1) result[v]+=simd_shuffle_down(result[v],off);
73
+ if(kl==0 && vec0+v<M) out[(ulong)(vec0+v)*N+row]=T(result[v]);
74
+ }
75
+ ` });
76
+ }
77
+ /** Requires the caller to prove aligned, row-contiguous x, as Qwen does
78
+ * with its RMSNorm output. Preserve native fallback when that proof is absent. */
79
+ export function wideTrellisPrefill(x: MlxArray, codes: MlxArray, scales: MlxArray, g: TrellisGeometry): MlxArray {
80
+ const [m, k] = x.shape;
81
+ if (x.ndim !== 2 || x.dtype !== Dtype.bfloat16 || !m || m < 5 || m > 15 ||
82
+ k !== 5120 || g.inFeatures !== k || g.axis !== 1 || g.T !== 256 || g.L !== 12 ||
83
+ ![2, 3, 4].includes(g.k) || g.blockInterleave || g.rows < 4 || g.rows % 4 ||
84
+ g.outFeatures !== g.rows || g.cols !== k || codes.dtype !== Dtype.uint32 ||
85
+ codes.ndim !== 2 || codes.shape[0] !== g.rows || codes.shape[1] !== k * g.k / 32 || scales.size !== g.rows)
86
+ throw new Error("unsupported wide trellis prefill geometry, dtype or layout");
87
+ const kl = m <= 5 ? 32 : 16;
88
+ return wideKernel().apply([x, codes, scales], {
89
+ outputs: [{ shape: [m, g.outFeatures], dtype: x.dtype }],
90
+ grid: [32, g.outFeatures / 4 * (kl / 8), 1], threadGroup: [32, kl / 8, 1],
91
+ templateDtypes: { T: x.dtype },
92
+ templateInts: { M: m, SM: m, KL: kl, KC: k, N: g.outFeatures, BT: g.T, L: g.L, BITS: g.k },
93
+ })[0]!;
94
+ }
@@ -3,7 +3,7 @@
3
3
  // like qwen3.ts, with every arch delta selected by the UniversalArgs
4
4
  // descriptor (archs.ts). Monolith path only: no compiled
5
5
  // decode, no kv-quant — slow, never broken. The bar is L1 bit-exactness
6
- // vs mlx-lm on this machine's GPU (tests/universal-parity.test.ts).
6
+ // vs mlx-lm on this machine's GPU (tests/parity/universal-parity.test.ts).
7
7
  //
8
8
  // Porting discipline: each branch transcribes its mlx-lm source op-for-op
9
9
  // (q/k-norm before vs after reshape, rope order, scale points, softcap,
@@ -11,6 +11,7 @@ import type { Weights } from "../../weights";
11
11
  import { MlxArray } from "../../mlx/array";
12
12
  import * as ops from "../../mlx/ops";
13
13
  import { QuantizedEmbedding, QuantizedLinear, RMSNorm } from "../gemma4-base";
14
+ import { TrellisLinear } from "../trellis-linear";
14
15
 
15
16
  /** Records every tensor a universal load consumes; `finish` diffs against
16
17
  * the shard index so a descriptor mistake is a LOAD error (unconsumed /
@@ -66,7 +67,7 @@ export class DenseLinear {
66
67
  }
67
68
 
68
69
  /** Either linear flavor behind one call surface. */
69
- export type AnyLinear = QuantizedLinear | DenseLinear;
70
+ export type AnyLinear = QuantizedLinear | DenseLinear | TrellisLinear;
70
71
 
71
72
  /** Quantized when `.scales` exists (MLX-quantized checkpoints), else dense
72
73
  * bf16/f16/f32 (Phase 1.5). Both flavors carry the optional ADDITIVE
@@ -74,6 +75,12 @@ export type AnyLinear = QuantizedLinear | DenseLinear;
74
75
  export function loadLinear(
75
76
  weights: Weights, path: string, config: ModelConfig, audit: WeightAudit,
76
77
  ): AnyLinear {
78
+ if (TrellisLinear.isTrellis(config, path)) {
79
+ const lin = TrellisLinear.load(weights, path, config);
80
+ audit.use(`${path}.weight`);
81
+ audit.use(`${path}.scales`);
82
+ return lin;
83
+ }
77
84
  if (weights.has(`${path}.scales`)) {
78
85
  const lin = QuantizedLinear.load(weights, path, config);
79
86
  audit.use(`${path}.weight`);
@@ -8,7 +8,7 @@
8
8
  //
9
9
  // Resolution order for the libs (mirrored in src/mlx/ffi.ts):
10
10
  // MLX_BUN_LIBMLXC env > beside the executable (sidecar dist/ layout,
11
- // docs/reference/embedding.md) > the native-pack cache dir > homebrew.
11
+ // docs/reference/distribution.md) > the native-pack cache dir > homebrew.
12
12
  // Dev trees and embedders therefore never download anything.
13
13
 
14
14
  import { existsSync, mkdirSync, renameSync, rmSync } from "node:fs";
@@ -17,22 +17,23 @@ import { dirname, join } from "node:path";
17
17
  import { downloadOne } from "./download";
18
18
  import { runtimeValue } from "./runtime-config";
19
19
 
20
- export const NATIVE_PACK_VERSION = "0.3.0";
20
+ export const NATIVE_PACK_VERSION = "0.4.0";
21
+ export const MLX_CORE_VERSION = "0.32.2";
21
22
  export const NATIVE_PACK_FILES = [
22
23
  "libmlxc.dylib", "libmlx.dylib", "libjaccl.dylib", "mlx.metallib",
23
24
  "libmlx_bun_expert_io.dylib", "mlx-bun-frame-extract",
24
25
  ] as const;
25
26
 
26
27
  // RELEASE-TIME BAKE: scripts/build-native-pack.sh prints the sha256/size for
27
- // the freshly built tarball — publish `native-v0.3.0` with both assets and
28
+ // the freshly built tarball — publish `native-v0.4.0` with both assets and
28
29
  // paste the values here BEFORE tagging the package release (the download
29
30
  // 404s until the GitHub release exists; dev trees resolve via homebrew/env
30
31
  // and never hit this path).
31
32
  const SHA256: Record<string, string> = {
32
- arm64: "b485fed858971ea6f072f349a0234c0e2d5030d3ef2ddfa942c8975eca2d3b2e",
33
+ arm64: "f2b0857658aa0826aee14b94a6b83d4c86c08f2c07d716991de6afd038f5ca5d",
33
34
  };
34
35
  const SIZE: Record<string, number> = {
35
- arm64: 52299265,
36
+ arm64: 41931583,
36
37
  };
37
38
 
38
39
  export function nativePackName(arch = process.arch): string {
@@ -17,37 +17,11 @@ import { AuthStorage, ModelRegistry } from "@earendil-works/pi-coding-agent";
17
17
  * derive it from the registry's `find()` return type. */
18
18
  export type PiModel = NonNullable<ReturnType<ModelRegistry["find"]>>;
19
19
 
20
- // Provider/model constants — kept aligned with src/harness-pi.ts so every
21
- // path (web embed, terminal embed, external pi) registers the same provider.
22
- export const PI_PROVIDER_ID = "mlx-bun";
23
- export const PI_LOCAL_MODEL_ID = "local";
24
- export const PI_API_KEY = "sk-mlx-bun-local";
25
- export const PI_API = "openai-completions" as const;
26
-
27
- export const DEFAULT_CONTEXT_WINDOW = 32_768;
28
- export const DEFAULT_MAX_TOKENS = 8_192;
29
-
30
- export interface PiProviderOptions {
31
- /** Advertised context window. Default: DEFAULT_CONTEXT_WINDOW. */
32
- contextWindow?: number;
33
- /** Advertised max output tokens. Default: DEFAULT_MAX_TOKENS. */
34
- maxTokens?: number;
35
- /** Human-readable model label (e.g. the real repo id). Default: "mlx-bun (local)". */
36
- name?: string;
37
- /** The loaded model has a switchable reasoning channel (server's
38
- * `ctx.template.supportsThinking` — true for any model whose chat template
39
- * gates on enable_thinking: Qwen3.5, MiniCPM5, …). When true Pi engages
40
- * reasoning and sends the on/off switch the server maps to enable_thinking.
41
- * Default false. */
42
- reasoning?: boolean;
43
- /** The loaded model can accept images (server's `ctx.vision != null`).
44
- * This MUST match the real model: pi-ai serializes user content as OpenAI
45
- * multimodal content-parts (`[{type:"text",…}]`) for image-capable models
46
- * and as a plain string otherwise. A text-only chat template renders nothing
47
- * for a parts array, so declaring a non-vision model as image-capable makes
48
- * the user's turn vanish ("I don't see any message"). Default false. */
49
- vision?: boolean;
50
- }
20
+ import { PI_PROVIDER_ID, PI_API_KEY, PI_API, PI_LOCAL_MODEL_ID, piModelDefinition,
21
+ type PiModelOptions } from "./contracts/pi-provider";
22
+ export { PI_PROVIDER_ID, PI_API_KEY, PI_API, PI_LOCAL_MODEL_ID,
23
+ DEFAULT_CONTEXT_WINDOW, DEFAULT_MAX_TOKENS } from "./contracts/pi-provider";
24
+ export interface PiProviderOptions extends PiModelOptions {}
51
25
 
52
26
  export interface PiProviderWiring {
53
27
  authStorage: AuthStorage;
@@ -70,39 +44,7 @@ export function buildPiProvider(baseUrl: string, opts: PiProviderOptions = {}):
70
44
  baseUrl,
71
45
  apiKey: PI_API_KEY,
72
46
  api: PI_API,
73
- models: [
74
- {
75
- id: PI_LOCAL_MODEL_ID,
76
- name: opts.name ?? "mlx-bun (local)",
77
- api: PI_API,
78
- reasoning: opts.reasoning ?? false,
79
- // Compat flags that make pi emit the wire format our local server's
80
- // chat templates expect (docs/custom-provider.md "Model Definition
81
- // Reference"):
82
- // - supportsDeveloperRole:false → pi sends the system prompt as the
83
- // `system` role, not `developer` (our templates only know
84
- // system/user/assistant/tool).
85
- // - thinkingFormat:"qwen-chat-template" (reasoning models only) → pi
86
- // sends chat_template_kwargs.enable_thinking, the boolean our
87
- // Qwen3.5/MiniCPM5 templates gate on. pi computes the boolean from
88
- // the selected thinking level (off → false), so no thinkingLevelMap.
89
- compat: {
90
- supportsDeveloperRole: false,
91
- ...(opts.reasoning ? { thinkingFormat: "qwen-chat-template" as const } : {}),
92
- },
93
- // input MUST match the loaded model. pi-ai serializes user content as a
94
- // plain string for text-only models and as OpenAI content-parts
95
- // (`[{type:"text",…}]`) once a model declares image input — and a
96
- // text-only chat template renders NOTHING for a parts array, dropping
97
- // the user's turn ("I don't see any message"). So declare image input
98
- // only for an actual vision model; the web `ready` frame's `vision`
99
- // flag is the same signal that drives the UI's image affordance.
100
- input: opts.vision ? ["text", "image"] : ["text"],
101
- cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
102
- contextWindow: opts.contextWindow ?? DEFAULT_CONTEXT_WINDOW,
103
- maxTokens: opts.maxTokens ?? DEFAULT_MAX_TOKENS,
104
- },
105
- ],
47
+ models: [piModelDefinition(opts)],
106
48
  });
107
49
 
108
50
  const model = modelRegistry.find(PI_PROVIDER_ID, PI_LOCAL_MODEL_ID);
package/src/pi-web.ts CHANGED
@@ -1,3 +1,7 @@
1
+ import { APP_ROUTE_IDS } from "./contracts/pi";
2
+ export { APP_ROUTE_IDS } from "./contracts/pi";
3
+ import type { ApprovalDecision, ImageAttachment, ClientMessage, UiSnapshotElement, UiSnapshot, WizardStep, AppUiContext, AppRouteId, ReadyGenDefaults, ServerMessage, HistoryToolItem, HistoryItem, SessionListItem, SamplingOverrides, Lane } from "./contracts/pi";
4
+ export type { ApprovalDecision, ImageAttachment, ClientMessage, UiSnapshotElement, UiSnapshot, WizardStep, AppUiContext, AppRouteId, ReadyGenDefaults, ServerMessage, HistoryToolItem, HistoryItem, SessionListItem, SamplingOverrides } from "./contracts/pi";
1
5
  // mlx-bun pi-web — server-side embed of the pi agent SDK that bridges a
2
6
  // browser WebSocket to an in-process AgentSession driving mlx-bun's own
3
7
  // local model (Phase 16, web half).
@@ -52,7 +56,7 @@ import { MEMORY_TOOL_NAMES, REFERENCE_TOOL_NAMES } from "./memory/tools";
52
56
  import { buildPiAgentSurface } from "./pi-session";
53
57
  import { buildPiProvider, DEFAULT_CONTEXT_WINDOW, PI_LOCAL_MODEL_ID } from "./pi-provider";
54
58
  import { downloadsSnapshot } from "./download";
55
- import { getLane, type Lane } from "./serve/lane-registry";
59
+ import { getLane } from "./serve/lane-registry";
56
60
  import { isToolAlwaysAllowed, setToolAlwaysAllowed, listAlwaysAllowedTools } from "./tool-approvals";
57
61
 
58
62
  /**
@@ -356,188 +360,7 @@ export interface PiWsData {
356
360
 
357
361
  type PiWs = ServerWebSocket<PiWsData>;
358
362
 
359
- // ---- WS protocol message shapes --------------------------------------
360
-
361
- /** Decision a browser returns for a gated tool call. `editedArgs`, when
362
- * present on an "allow", replaces the tool's proposed input before it runs
363
- * (LM Studio's editable-args pattern — plan §5.4/§6.5). `alwaysAllow` marks
364
- * the decision as "and remember this tool" — handled by resolveApproval,
365
- * which persists it to the durable tool-approvals config (src/tool-
366
- * approvals.ts) before settling the pending promise. */
367
- export type ApprovalDecision = "allow" | "deny";
368
-
369
- /** An image attachment from the browser: base64 (no data: prefix) + mime. */
370
- export interface ImageAttachment {
371
- data: string;
372
- mimeType: string;
373
- }
374
-
375
- /** Client -> server frames. Exported type-only for the web frontend
376
- * (src/web/src/*.ts import it via `import type` — see tsconfig.web.json
377
- * and scripts/build-web.ts) so the WS contract is checked at compile time
378
- * on both ends instead of drifting between server TS and untyped inline
379
- * browser JS (the observed Phase-0 bug class). */
380
- export type ClientMessage =
381
- | { type: "prompt"; text: string; images?: ImageAttachment[] }
382
- | { type: "abort" }
383
- // `editedArgs`: when the user edited the proposed arguments in the
384
- // approval card's textarea before approving, the (re-parsed) JSON object
385
- // to substitute for the tool's original input — mutated onto the pi SDK's
386
- // ToolCallEvent.input in place (the SDK's documented mutation contract;
387
- // see installApprovalGate). Ignored on "deny". `alwaysAllow`: persist this
388
- // tool to the durable always-allow config (src/tool-approvals.ts) so
389
- // future calls to the SAME TOOL NAME skip the card entirely, on this and
390
- // every other session.
391
- | { type: "approval"; callId: string; decision: ApprovalDecision; editedArgs?: Record<string, unknown>; alwaysAllow?: boolean }
392
- // Toggle the model's reasoning channel on/off (only meaningful when the
393
- // model supports thinking — ready.thinking). Maps to Pi's session thinking
394
- // level: "medium" (on) ↔ "off". Pi sends it as enable_thinking to the server.
395
- | { type: "set_thinking"; enabled: boolean }
396
- // Opt-in toggle (plan §5.4/§6.5/§9 Phase 2, default OFF): "let the agent
397
- // touch files on this machine". Persisted per-browser by the client
398
- // (localStorage `mlxbun.codingTools`); the ALLOWLIST decision is enforced
399
- // here, server-side. buildPiAgentSurface's tool list is fixed for the
400
- // life of an AgentSession (pi's DefaultResourceLoader config is baked in
401
- // at createAgentSessionServices time), so this can't rewire an
402
- // in-progress session — it takes effect starting with the next
403
- // new_session/open_session/fork_session (createRuntimeFactory reads
404
- // this.codingToolsRequested fresh every time the SDK calls it, including
405
- // from AgentSessionRuntime.newSession()'s internal re-invocation). The
406
- // `coding_tools` ServerMessage below reports the honest current-vs-pending
407
- // state so the UI can say so instead of implying it applies immediately.
408
- | { type: "set_coding_tools"; enabled: boolean }
409
- // Select the active LoRA adapter for subsequent turns (null = none/base).
410
- // app.html mounts it (POST /v1/adapters) before sending this; the
411
- // before_provider_request hook injects it into the provider payload.
412
- | { type: "set_adapter"; id: string | null }
413
- // Per-request sampling overrides. Each field is optional; null/undefined
414
- // means "leave it to the server default" (the mode-aware recommended
415
- // value resolved in toOptions). A present numeric value is injected into
416
- // the provider payload and always wins. The before_provider_request hook
417
- // injects whatever is set here.
418
- //
419
- // `scope` (plan §9 Phase 3 / beat matrix Axis 4 "per-message sampling
420
- // scope"): "session" (default, omitting the field means this too) persists
421
- // the override on the connection until changed — today's behavior,
422
- // unchanged. "next_turn" stores a ONE-SHOT override applied to exactly
423
- // the next prompt's injection, then cleared automatically — it never
424
- // touches the session-level values (a later plain set_sampling with
425
- // scope:"session" or omitted still sees whatever was there before this
426
- // message). Composition when both are present: next_turn's SET fields
427
- // win over session's for that one request; anything next_turn leaves
428
- // null/unset falls back to the session-level override for that field
429
- // (composeSampling — session-level itself falls back to the server's
430
- // mode-aware default per injectSampling's existing contract).
431
- | {
432
- type: "set_sampling";
433
- scope?: "session" | "next_turn";
434
- temperature?: number | null;
435
- top_p?: number | null;
436
- top_k?: number | null;
437
- min_p?: number | null;
438
- xtc_probability?: number | null;
439
- xtc_threshold?: number | null;
440
- repetition_penalty?: number | null;
441
- repetition_context_size?: number | null;
442
- presence_penalty?: number | null;
443
- frequency_penalty?: number | null;
444
- seed?: number | null;
445
- }
446
- // Per-session USER system prompt, layered ON TOP OF (not replacing) the
447
- // built-in surface prompt (buildWebChatSystemPrompt + memoryHint). null
448
- // clears it. Applied by the before_agent_start hook on the NEXT turn (pi
449
- // calls it fresh inside every session.prompt(), so this is a true
450
- // per-turn injection, not a session-creation-only setting — see
451
- // installSystemPromptHook). Presets v1 (composer.ts) send this alongside
452
- // set_sampling to apply a saved bundle.
453
- | { type: "set_system_prompt"; text: string | null }
454
- // Session management (recent-chats sidebar + new chat).
455
- | { type: "new_session" }
456
- | { type: "list_sessions" }
457
- | { type: "open_session"; path: string }
458
- | { type: "fork_session"; path: string }
459
- | { type: "delete_session"; path: string }
460
- // Message actions (plan §5.2). All three navigate the session's leaf
461
- // pointer via AgentSession.navigateTree() then re-prompt — a sibling
462
- // branch in the SAME session file, never a new one (see the helpers above
463
- // findLastUserMessageEntry for the full rationale). Regenerate re-sends the
464
- // last user message's original content verbatim; edit_resend re-sends it
465
- // with edited text (images preserved); switch_sibling moves the active
466
- // leaf to a previously-created sibling (an earlier edit/regeneration)
467
- // without sending anything new.
468
- | { type: "regenerate" }
469
- | { type: "edit_resend"; text: string }
470
- | { type: "switch_sibling"; entryId: string }
471
- // App-aware assistant (plan §6.6, §9 Phase 3, beat matrix Axis 12): pushed
472
- // by chat.ts on every route change AND on wizard-step change (quantize/
473
- // finetune/dataset), never on a timer. `context` is opaque here (the
474
- // browser's AppContext shape, src/web/src/assistant.ts) — stored verbatim
475
- // as the connection's currentAppContext and returned by the
476
- // get_current_app_context tool, plus mined for the compact one-line
477
- // ambient context auto-prepended to the NEXT prompt (see
478
- // installAppContextHook / ambientContextLine). This is a DIFFERENT frame
479
- // from the server->client `context` ServerMessage (context-window usage,
480
- // above) — same name, opposite direction, disambiguated by which type
481
- // union it's a member of; the two are never in scope at the same call
482
- // site (ClientMessage here vs. ServerMessage below).
483
- | { type: "context"; context: AppUiContext };
484
-
485
- // ---- App-aware assistant (plan §6.6, §9 Phase 3, beat matrix Axis 12) --
486
- //
487
- // Mirrors src/web/src/assistant.ts's browser-side types (which are the
488
- // source of truth for shape — this server-side copy is intentionally loose
489
- // (mostly `unknown`/optional) since the server never constructs a
490
- // UiSnapshot itself, only stores and forwards what the browser sends. Kept
491
- // here (not imported) because src/web/src/assistant.ts is bundled into the
492
- // browser and importing it here would pull browser code into the server
493
- // build — the inverse of the type-only ClientMessage/ServerMessage import
494
- // direction the frontend uses for pi-web.ts.
495
-
496
- /** One element in a captured DOM snapshot — see assistant.ts's
497
- * UiSnapshotElement for the authoritative shape/doc. */
498
- export interface UiSnapshotElement {
499
- ref: string;
500
- tag: string;
501
- label: string;
502
- kind: "interactive" | "region";
503
- role?: string;
504
- selector: string;
505
- spotlightId?: string;
506
- }
507
-
508
- export interface UiSnapshot {
509
- route: string;
510
- capturedAt: string;
511
- elements: UiSnapshotElement[];
512
- }
513
-
514
- /** The wizard-step process-state fields (§6.6 "carries process state, not
515
- * just elements") — present only on routes with a step wizard. */
516
- export interface WizardStep {
517
- index: number;
518
- count: number;
519
- label: string;
520
- }
521
-
522
- /** The full context payload pushed by chat.ts on route/step change — see
523
- * assistant.ts's buildAppContext. Stored verbatim per-connection. */
524
- export interface AppUiContext {
525
- route: string;
526
- view?: string;
527
- step?: WizardStep;
528
- snapshot: UiSnapshot;
529
- }
530
-
531
- /** Route catalog mirror (src/web/src/ui-catalog.ts's ROUTE_IDS) for
532
- * server-side validation of navigate_app's route/page param BEFORE it
533
- * ever reaches the browser — an unknown route is rejected here as a tool
534
- * error rather than sent as a `ui_navigate` frame the router can't match.
535
- * Keep in sync with ui-catalog.ts's ROUTE_IDS by hand (no shared-JSON
536
- * import between the server and browser bundles — see that file's
537
- * top-of-file comment for why). */
538
- export const APP_ROUTE_IDS = ["chat", "quantize", "finetune", "dataset", "status"] as const;
539
- export type AppRouteId = (typeof APP_ROUTE_IDS)[number];
540
-
363
+ /** Validate assistant navigation against the shared browser/server route IDs. */
541
364
  export function isAppRouteId(v: string): v is AppRouteId {
542
365
  return (APP_ROUTE_IDS as readonly string[]).includes(v);
543
366
  }
@@ -570,117 +393,6 @@ export function ambientContextLine(ctx: AppUiContext | null | undefined): string
570
393
  return `[user is on: ${place}${stepPart}]`;
571
394
  }
572
395
 
573
- /** Model-author recommended sampling (generation_config.json, server-CLI
574
- * overrides applied) — sent once on `ready` so the sampling popover can show
575
- * per-model defaults instead of one hardcoded shape (web-ui-pass-plan.md
576
- * #14). A field is null when neither the model nor the server configured one
577
- * (the built-in fallback in toOptions still applies server-side). */
578
- export interface ReadyGenDefaults {
579
- temperature: number | null;
580
- topP: number | null;
581
- topK: number | null;
582
- }
583
-
584
- /** Server -> client frames. Exported type-only — see ClientMessage above. */
585
- export type ServerMessage =
586
- // `vision`: whether the loaded model can accept images (drives the UI's
587
- // image-attach affordance — false on e4b until the SigLIP sidecar lands).
588
- // `audio`: whether the loaded model can accept `input_audio` parts (audio
589
- // tower loaded or loadable). Discovery-only for now: pi's Model.input type
590
- // has no "audio" modality, so unlike `vision` this flag never feeds the
591
- // provider declaration — clients use it instead of probing for a 400.
592
- // `thinking`: whether the model has a switchable reasoning channel (drives
593
- // the UI's thinking on/off toggle; false hides it).
594
- | { type: "ready"; model: string; vision: boolean; audio: boolean; thinking: boolean; genDefaults: ReadyGenDefaults }
595
- | { type: "turn_start" }
596
- | { type: "text_delta"; delta: string }
597
- | { type: "thinking_delta"; delta: string }
598
- | { type: "tool_start"; callId: string; tool: string; args: unknown }
599
- | { type: "tool_approval_request"; callId: string; tool: string; args: unknown }
600
- | { type: "tool_update"; callId: string; chunk: unknown }
601
- | { type: "tool_end"; callId: string; ok: boolean; result: unknown }
602
- // `lane`: the serving lane the just-finished turn ran on (serial /
603
- // serial+spec / batched), correlated via the lane registry keyed by the
604
- // AssistantMessage's responseId (the pi SDK's own usage parsing drops
605
- // custom fields — see src/serve/lane-registry.ts). Absent when the turn
606
- // produced no assistant message with a responseId (e.g. an aborted turn
607
- // before any model call) — never guessed client-side (risk #5).
608
- | { type: "turn_end"; lane?: Lane }
609
- | { type: "queue_update"; steering: readonly string[]; followUp: readonly string[] }
610
- // Replay a session's transcript (rebuilds the thread); and the sidebar list.
611
- | { type: "history"; items: HistoryItem[] }
612
- | { type: "sessions"; items: SessionListItem[]; activePath?: string }
613
- // Sibling group for the LAST user message (edit-and-resend's `< i/n >`
614
- // toggle, plan §5.2). Sent after every history rebuild and after a
615
- // completed turn. `count` <= 1 means no toggle to show (the common case:
616
- // no edits/regenerations yet). `entryId` is the currently-active sibling's
617
- // id; `siblingIds` is the full ordered group so the client can resolve
618
- // "previous/next" locally and send switch_sibling with a concrete id
619
- // (never inferred/guessed — the server is the source of truth for order).
620
- | { type: "siblings"; entryId?: string; index: number; count: number; siblingIds: string[] }
621
- // Context-window usage indicator. tokens/percent are null right after a
622
- // compaction until the next assistant reply (pi can't estimate yet).
623
- | { type: "context"; tokens: number | null; contextWindow: number; percent: number | null }
624
- // Effective vs. pending codingTools state (set_coding_tools above).
625
- // `active` is what the CURRENT session actually built with (gates
626
- // whether the approval card can ever fire); `pending` is the browser's
627
- // most recent request, which only takes effect on the next
628
- // new/opened/forked session — surfaced so the settings copy can say
629
- // "will apply to your next new chat" instead of lying about immediacy.
630
- | { type: "coding_tools"; active: boolean; pending: boolean }
631
- // Full always-allow set for THIS machine (src/tool-approvals.ts), sent on
632
- // `ready` and after any change, so the settings panel can list/forget
633
- // durable per-tool approvals without a separate REST round-trip.
634
- | { type: "tool_approvals"; alwaysAllow: string[] }
635
- // App-aware assistant (plan §6.6): the navigate_app / spotlight_ui tools'
636
- // side-channel notifications, mapped 1:1 from the tool call's params —
637
- // never guessed or re-derived, so chat.ts's router/spotlight get exactly
638
- // what the model asked for. `route` here is always a validated
639
- // AppRouteId (isAppRouteId already ran server-side before this frame is
640
- // ever sent — see navigate_app's execute). ui_spotlight's fields mirror
641
- // SpotlightRequest 1:1 (assistant.ts resolves ref/label/selector/target
642
- // client-side, in that precedence order, against its last snapshot).
643
- | { type: "ui_navigate"; route: AppRouteId }
644
- | { type: "ui_spotlight"; ref?: string; label?: string; selector?: string; target?: string; message?: string; route?: AppRouteId }
645
- | { type: "error"; message: string };
646
-
647
- // ---- Session serialization (pure, unit-tested) -----------------------
648
-
649
- /** A finished tool call as rendered in replayed history. */
650
- export interface HistoryToolItem {
651
- callId: string;
652
- name: string;
653
- args: unknown;
654
- /** Tool result text, filled from the matching toolResult message. */
655
- result: string;
656
- }
657
-
658
- /** One replayed transcript turn: a user or assistant message (assistant
659
- * messages carry any tool calls they made, with results merged in). */
660
- export interface HistoryItem {
661
- role: "user" | "assistant";
662
- text: string;
663
- /** Model reasoning/thinking, kept separate from the final answer. */
664
- thinking?: string;
665
- tools: HistoryToolItem[];
666
- /** Session entry id (user items only). Lets the browser correlate "the
667
- * last user message" in the replayed thread with the `siblings` frame's
668
- * entryId, so it knows which DOM node gets the edit/sibling-toggle UI
669
- * (plan §5.2). Undefined for assistant items — they aren't fork targets. */
670
- entryId?: string;
671
- }
672
-
673
- /** A row in the recent-chats sidebar, derived from pi's SessionInfo. */
674
- export interface SessionListItem {
675
- path: string;
676
- id: string;
677
- title: string;
678
- /** Last-modified epoch ms (newest first). */
679
- modified: number;
680
- messageCount: number;
681
- forked: boolean;
682
- }
683
-
684
396
  /** Flatten a content value (string | content-parts) to its text. */
685
397
  function contentText(content: unknown): string {
686
398
  if (typeof content === "string") return content;
@@ -964,25 +676,6 @@ export function injectAdapter(
964
676
  return { ...payload, adapter: selected };
965
677
  }
966
678
 
967
- /** Per-request sampling overrides carried on the session and injected into the
968
- * provider payload by the before_provider_request hook. Each field is either a
969
- * user-set override or null/undefined ("use the server's mode-aware default").
970
- * Field names match the server's ChatRequest wire names (not toOptions' camelCase)
971
- * since these are injected directly into the outgoing chat-completions payload. */
972
- export interface SamplingOverrides {
973
- temperature?: number | null;
974
- top_p?: number | null;
975
- top_k?: number | null;
976
- min_p?: number | null;
977
- xtc_probability?: number | null;
978
- xtc_threshold?: number | null;
979
- repetition_penalty?: number | null;
980
- repetition_context_size?: number | null;
981
- presence_penalty?: number | null;
982
- frequency_penalty?: number | null;
983
- seed?: number | null;
984
- }
985
-
986
679
  /** Every SamplingOverrides field name, in wire order — the single source of
987
680
  * truth `injectSampling` and the `set_sampling` handler both walk, so a new
988
681
  * field only needs adding here + the interface above. */
@@ -1158,7 +851,7 @@ function appAwareTextResult(text: string, details: unknown = {}): { content: [{
1158
851
  * null before the first one arrives); `notify` sends a ServerMessage
1159
852
  * (ui_navigate/ui_spotlight) to that same browser tab. Pure enough to
1160
853
  * unit test the tool -> frame mapping via a fake notify collecting sent
1161
- * frames (tests/pi-web.test.ts). */
854
+ * frames (tests/research/pi-web.test.ts). */
1162
855
  export function createAppAwareTools(
1163
856
  getContext: () => AppUiContext | null,
1164
857
  notify: (msg: ServerMessage) => void,
@@ -1267,7 +960,7 @@ class PiWebSession {
1267
960
  * "Per-message sampling scope" block comment above composeSampling for
1268
961
  * the full lifecycle. Wrapped in SamplingScopeState (not two loose
1269
962
  * fields) so the exact same applySetSampling/consumeForRequest pure
1270
- * functions run here and in tests/pi-web.test.ts. */
963
+ * functions run here and in tests/research/pi-web.test.ts. */
1271
964
  private samplingScope: SamplingScopeState = initialSamplingScopeState();
1272
965
  /** User's custom system-prompt text for this connection (set via
1273
966
  * set_system_prompt), null = none. Read by the before_agent_start hook