mlx-bun 0.0.10 → 0.0.12

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 (216) hide show
  1. package/README.md +107 -32
  2. package/THIRD_PARTY_LICENSES.md +10 -0
  3. package/docs/archive/mlx-bun-lab-report.html +275 -0
  4. package/docs/archive/wwdc-mlx-bun.html +848 -0
  5. package/docs/design/audio-input-plan.md +356 -0
  6. package/docs/design/batching-perf-path.md +58 -35
  7. package/docs/design/batching-v2-plan.md +23 -9
  8. package/docs/design/bucketing-stage.md +2 -1
  9. package/docs/design/colibri-glm52-port.md +1479 -0
  10. package/docs/design/compat-cli-surface-design.md +4 -3
  11. package/docs/design/decode-speed-program.md +172 -0
  12. package/docs/design/docs-reorg-plan.md +1 -1
  13. package/docs/design/dspark-serving-program.md +371 -0
  14. package/docs/design/dspark-speculative-decoding.md +167 -5
  15. package/docs/design/faithful-l1-consolidation.md +310 -0
  16. package/docs/design/generic-model-support.md +8 -5
  17. package/docs/design/grammar-spec-batching-integration.md +301 -0
  18. package/docs/design/minicpm5-decode-megakernel.md +16 -11
  19. package/docs/design/mlx-lm-tool-parity-plan.md +11 -1
  20. package/docs/design/omlx-adoption-map.md +14 -6
  21. package/docs/design/orpo-training.md +34 -30
  22. package/docs/design/paged-kv-cache.md +214 -0
  23. package/docs/design/parallel-slots.md +82 -49
  24. package/docs/design/parity-tier-dag.md +43 -33
  25. package/docs/design/pre-colibri-stabilization.md +329 -0
  26. package/docs/design/repo-cleanup-plan.md +189 -0
  27. package/docs/design/runtime-isolation.md +19 -3
  28. package/docs/design/segmented-backward-training.md +3 -2
  29. package/docs/design/ssd-kv-cold-tier.md +130 -0
  30. package/docs/design/structured-output.md +484 -0
  31. package/docs/design/the-dreaming-master-plan.md +2 -3
  32. package/docs/design/turboquant-kv.md +235 -0
  33. package/docs/design/unified-engine-frontier-plan.md +774 -0
  34. package/docs/design/web-chat-beat-matrix.md +315 -0
  35. package/docs/design/web-chat-redesign.md +1112 -0
  36. package/docs/investigations/colibri-oracle-pin.md +1284 -0
  37. package/docs/investigations/dspark-handoff.md +182 -16
  38. package/docs/investigations/indexshare-performance-spike.md +357 -0
  39. package/docs/investigations/lab-build-journal.md +2 -2
  40. package/docs/investigations/openwiki-evaluation.md +249 -0
  41. package/docs/planning/IDEAS.md +14 -0
  42. package/docs/planning/PRODUCT_ROADMAP.md +2 -3
  43. package/docs/planning/chat-ui-vision-memory-first.md +199 -0
  44. package/docs/planning/chat-ui-vision.md +50 -0
  45. package/docs/planning/memory-docs-and-dag-plan.md +15 -6
  46. package/docs/planning/release-notes-v0.0.11.md +239 -0
  47. package/docs/planning/release-notes-v0.0.12.md +80 -0
  48. package/docs/reference/cli.md +63 -18
  49. package/docs/reference/distribution.md +37 -6
  50. package/docs/reference/embedding.md +40 -16
  51. package/docs/reference/features-matrix.md +99 -0
  52. package/docs/reference/library-api.md +69 -20
  53. package/docs/reference/memory.md +26 -6
  54. package/docs/reference/models.md +60 -3
  55. package/docs/reference/orpo-quickstart.md +3 -4
  56. package/docs/reference/server-api.md +717 -24
  57. package/docs/reference/server-config.md +265 -129
  58. package/docs/reference/training.md +24 -16
  59. package/package.json +5 -3
  60. package/src/anthropic.ts +67 -44
  61. package/src/audio/conformer.ts +647 -0
  62. package/src/audio/decode.ts +133 -0
  63. package/src/audio/features.ts +202 -0
  64. package/src/audio/transcode.ts +67 -0
  65. package/src/chat-template.ts +155 -10
  66. package/src/cli.ts +483 -113
  67. package/src/config.ts +116 -24
  68. package/src/diffusion/diffusion-generate.ts +14 -8
  69. package/src/download.ts +143 -12
  70. package/src/eval/README.md +7 -6
  71. package/src/eval/ifeval.ts +63 -120
  72. package/src/eval/kl.ts +59 -3
  73. package/src/eval/quality-db.ts +2 -2
  74. package/src/eval/runner.ts +17 -8
  75. package/src/eval/tasks/ifeval.ts +167 -31
  76. package/src/expert-io.ts +391 -0
  77. package/src/expert-offload.ts +15 -2
  78. package/src/expert-residency.ts +1134 -0
  79. package/src/expert-usage.ts +415 -0
  80. package/src/flags.ts +13 -0
  81. package/src/generate.ts +438 -35
  82. package/src/grammar.ts +585 -0
  83. package/src/hf-push.ts +1 -1
  84. package/src/hub-rest.ts +242 -0
  85. package/src/index.ts +14 -2
  86. package/src/jobs/runner.ts +37 -18
  87. package/src/kv-store.ts +586 -93
  88. package/src/lora.ts +85 -14
  89. package/src/md-modules.d.ts +42 -0
  90. package/src/media-fetch.ts +426 -0
  91. package/src/memory/rest.ts +245 -0
  92. package/src/memory/vault.ts +74 -0
  93. package/src/mlx/array.ts +149 -19
  94. package/src/mlx/autograd.ts +53 -16
  95. package/src/mlx/checkpoint.ts +46 -13
  96. package/src/mlx/custom-vjp.ts +83 -35
  97. package/src/mlx/ffi.ts +27 -3
  98. package/src/mlx/metal-kernel.ts +7 -4
  99. package/src/mlx/ops.ts +153 -23
  100. package/src/mlx/turboquant-ops.ts +831 -0
  101. package/src/mlx/turboquant-tables.ts +237 -0
  102. package/src/model/batched-mask.ts +62 -1
  103. package/src/model/batched-quant.ts +206 -0
  104. package/src/model/batched-rotating-quant.ts +346 -0
  105. package/src/model/batched-rotating.ts +46 -1
  106. package/src/model/compiled-decode.ts +4 -15
  107. package/src/model/factory.ts +103 -6
  108. package/src/model/gemma4-base.ts +395 -39
  109. package/src/model/gemma4.ts +122 -33
  110. package/src/model/generated/gemma4-12b.ts +9 -13
  111. package/src/model/generated/gemma4-26b.ts +10 -5
  112. package/src/model/generated/gemma4-e4b.ts +14 -9
  113. package/src/model/glm52-atlas.ts +511 -0
  114. package/src/model/glm52-cache.ts +706 -0
  115. package/src/model/glm52-config.ts +262 -0
  116. package/src/model/glm52-container.ts +305 -0
  117. package/src/model/glm52-coupling.ts +388 -0
  118. package/src/model/glm52-dsa.ts +602 -0
  119. package/src/model/glm52-expert-layout.ts +253 -0
  120. package/src/model/glm52-layout.ts +152 -0
  121. package/src/model/glm52-memory.ts +473 -0
  122. package/src/model/glm52-mla.ts +786 -0
  123. package/src/model/glm52-moe.ts +223 -0
  124. package/src/model/glm52-pilot.ts +537 -0
  125. package/src/model/glm52-reference.ts +445 -0
  126. package/src/model/glm52-residency.ts +557 -0
  127. package/src/model/glm52-resident-weights.ts +135 -0
  128. package/src/model/glm52-streamed-experts.ts +453 -0
  129. package/src/model/glm52-streamed-metal.ts +610 -0
  130. package/src/model/glm52-weights.ts +503 -0
  131. package/src/model/glm52.ts +1153 -0
  132. package/src/model/minicpm5.ts +58 -102
  133. package/src/model/paged-kv.ts +253 -0
  134. package/src/model/qwen3-delta.ts +76 -15
  135. package/src/model/qwen3-moe.ts +404 -0
  136. package/src/model/qwen3.ts +9 -8
  137. package/src/model/qwen3_5.ts +88 -23
  138. package/src/model/support.ts +19 -1
  139. package/src/model/universal/dense.ts +40 -5
  140. package/src/model/universal/rope.ts +36 -21
  141. package/src/native/expert_io.c +523 -0
  142. package/src/native-pack.ts +6 -4
  143. package/src/pi-web.ts +1299 -82
  144. package/src/preflight.ts +27 -8
  145. package/src/prompt-cache.ts +293 -33
  146. package/src/quantize/quantizer.ts +16 -1
  147. package/src/registry.ts +57 -2
  148. package/src/responses.ts +62 -49
  149. package/src/serve/batch-scheduler.ts +1050 -78
  150. package/src/serve/generation-gateway.ts +252 -24
  151. package/src/serve/isolate.ts +417 -0
  152. package/src/serve/lane-registry.ts +44 -0
  153. package/src/serve/session-search.ts +326 -0
  154. package/src/server.ts +1701 -175
  155. package/src/spec/assistant-source.ts +150 -0
  156. package/src/spec/deepspec-source.ts +143 -0
  157. package/src/spec/dflash-source.ts +124 -0
  158. package/src/spec/dspark/ab-stats.ts +136 -0
  159. package/src/spec/dspark/calibration.ts +112 -0
  160. package/src/spec/dspark/deepspec-module.ts +801 -0
  161. package/src/spec/dspark/generate-dflash.ts +72 -20
  162. package/src/spec/dspark/loader.ts +23 -0
  163. package/src/spec/dspark/module-dflash.ts +278 -36
  164. package/src/spec/dspark/quantize-drafter.ts +91 -0
  165. package/src/spec/generate.ts +59 -25
  166. package/src/spec/glm52-mtp-source.ts +392 -0
  167. package/src/spec/ngram-source.ts +132 -0
  168. package/src/spec/serve-loop.ts +617 -0
  169. package/src/spec/source.ts +118 -0
  170. package/src/spec/two-model.ts +171 -0
  171. package/src/ssd-cache.ts +111 -35
  172. package/src/tokenizer.ts +288 -2
  173. package/src/tool-approvals.ts +131 -0
  174. package/src/tool-call.ts +438 -22
  175. package/src/train/flash-cce.ts +2 -2
  176. package/src/train/job.ts +0 -12
  177. package/src/train/lora-params.ts +17 -6
  178. package/src/train/trainer.ts +60 -30
  179. package/src/vision/prompt.ts +217 -48
  180. package/src/web/app.html +1197 -1791
  181. package/src/web/app.js +5694 -0
  182. package/src/web/icon.svg +13 -0
  183. package/src/web/manifest.webmanifest +24 -0
  184. package/src/web/src/adapters-panel.ts +288 -0
  185. package/src/web/src/api.ts +57 -0
  186. package/src/web/src/assistant.ts +485 -0
  187. package/src/web/src/chat.ts +1050 -0
  188. package/src/web/src/composer.ts +959 -0
  189. package/src/web/src/dataset.ts +132 -0
  190. package/src/web/src/finetune.ts +267 -0
  191. package/src/web/src/hub.ts +428 -0
  192. package/src/web/src/main.ts +98 -0
  193. package/src/web/src/markdown.ts +0 -0
  194. package/src/web/src/memory-panel.ts +572 -0
  195. package/src/web/src/model-picker.ts +159 -0
  196. package/src/web/src/palette.ts +346 -0
  197. package/src/web/src/protocol.ts +62 -0
  198. package/src/web/src/quantize.ts +167 -0
  199. package/src/web/src/rag.ts +274 -0
  200. package/src/web/src/sessions.ts +469 -0
  201. package/src/web/src/shell.ts +804 -0
  202. package/src/web/src/status.ts +300 -0
  203. package/src/web/src/ui-catalog.ts +92 -0
  204. package/src/web/sw.js +71 -0
  205. package/src/web/vendor/README.md +61 -0
  206. package/src/web/vendor/hljs-LICENSE +29 -0
  207. package/src/web/vendor/hljs-theme.css +59 -0
  208. package/src/web/vendor/hljs.js +26 -0
  209. package/src/web-tools.ts +54 -2
  210. package/src/weights.ts +82 -19
  211. package/src/model/fused-decode-kernel.ts +0 -312
  212. package/src/model/fused-geglu-kernel.ts +0 -198
  213. package/src/model/fused-mlp-kernel.ts +0 -169
  214. package/src/model/fused-swiglu-kernel.ts +0 -240
  215. package/src/model/steel-linear-kernel.ts +0 -96
  216. package/src/status-page.html +0 -497
@@ -22,7 +22,7 @@ Training is reachable several ways — all of them drive the **same** runner
22
22
 
23
23
  | Path | How | Use when |
24
24
  |---|---|---|
25
- | **CLI** | `mlx-bun train <model> --data <dir> [options]` — foreground, full ORPO stack on by default; auto-detects e4b/Gemma + sets its env; `--save-every`, `--resume`, `--dry-run`, `--sft-scope full\|response`, `--grad-accum`, `--grad-clip`, `--seed`, `--val-size`; streams loss to the terminal. `mlx-bun help train` for all flags. Run long jobs detached: `nohup mlx-bun train … &` | **The default for CLI users** |
25
+ | **CLI** | `mlx-bun train <model> --data <dir> [options]` — foreground, full ORPO stack on by default; auto-detects e4b/Gemma to pick sensible `--seq`/adapter-path defaults; no env flags needed; `--save-every`, `--resume`, `--dry-run`, `--sft-scope full\|response`, `--grad-accum`, `--grad-clip`, `--seed`, `--val-size`; streams loss to the terminal. `mlx-bun help train` for all flags. Run long jobs detached: `nohup mlx-bun train … &` | **The default for CLI users** |
26
26
  | **Web UI** | `mlx-bun serve`, open `/finetune` — pick model → dataset → hyperparameters → train; watch live train/val loss; merge/export the adapter | Interactive, in the browser |
27
27
  | **HTTP API** | `POST /api/finetune/submit` (job id + SSE events); `POST /api/finetune/inspect-dataset` to probe a file; `POST /api/finetune/merge` to fold an adapter into base weights | Scripted / remote |
28
28
  | **ORPO launcher** | [`scripts/train-orpo.ts`](../../scripts/train-orpo.ts) — the same stack driven by env vars (`MODEL=`, `DATA=`, `RANK=`, `RESUME=`, …). See [orpo-quickstart](./orpo-quickstart.md) | Env-var scripting |
@@ -167,7 +167,7 @@ are `DEFAULT_TRAIN_CONFIG` (trainer.ts:89).
167
167
  | `target_modules` | string[] | `q,k,v,o,gate,up,down _proj` | Which linears get adapters |
168
168
  | `num_layers` | int | `-1` | `-1` = all layers; `N` = last N only |
169
169
  | `iters` | int >0 | `100` | Total training steps |
170
- | `learning_rate` | float >0 | `2e-4` (sft) / `5e-5` (dpo) | AdamW LR |
170
+ | `learning_rate` | float >0 | `2e-4` (sft) / `1e-5` (orpo) / `5e-5` (dpo) | AdamW LR |
171
171
  | `max_seq_length` | int >0 | `512` | Truncate/pad sequences to this |
172
172
  | `batch_size` | int ≥1 | `1` | Rows per step (B=1 is the safe path; B>1 length-sorts + pads to 32) |
173
173
  | `grad_accumulation_steps` | int ≥1 | `1` | Accumulate grads over N micro-steps |
@@ -177,10 +177,14 @@ are `DEFAULT_TRAIN_CONFIG` (trainer.ts:89).
177
177
  | `weight_decay` | float ≥0 | `0.01` | AdamW weight decay (β = `[0.9, 0.999]`, fixed) |
178
178
  | `lora_dropout` | float [0,1) | `0.0` | LoRA-input dropout (PEFT-style), training-only regularizer for small sets. Recompute-safe (mask keyed by step+layer, so segmented/checkpointed backward reproduces it) |
179
179
  | `rs_lora` | bool | `false` | rsLoRA — scale the update by α/√rank instead of α, so `rank_scaling` changes capacity not step size. Recorded in the adapter config; inference applies the same per-layer scale |
180
- | `lora_plus_ratio` | float ≥1 | `1.0` | LoRA+ — LR multiplier for the B leaves (A stays at base LR). >1 speeds the B-driven early learning (B is zero-init). 1 = off. Wired in the ORPO loop |
180
+ | `lora_plus_ratio` | float ≥1 | `1.0` | LoRA+ — LR multiplier for the B leaves (A stays at base LR). >1 speeds the B-driven early learning (B is zero-init). 1 = off. The same optimizer policy applies to SFT, DPO, and ORPO |
181
181
  | `grad_checkpoint` | bool | `false` | Recompute layer activations in backward (memory↔compute; bit-identical) |
182
+ | `mlp_split` | bool | `false` | Gradient-checkpoint MLP splitting (Gemma4-only, requires `grad_checkpoint`) |
182
183
  | `segment_size` | int | `0` (off) | `>0` enables segmented backward — layers per segment (see below) |
183
184
  | `save_checkpoints` | bool | `false` | Save every eval-step checkpoint + write `metrics.json` |
185
+ | `grad_clip_norm` | float ≥0 | `1.0` | Gradient-norm clipping (on by default); `0` = off |
186
+ | `val_max_examples` | int >0 | `256` | Caps the validation-set size used per eval |
187
+ | `warm_start_adapter` | string | `""` (off) | Adapter dir to warm-start/resume LoRA weights from (the API field behind the CLI's `--resume`) |
184
188
  | `dpo_beta` | float >0 | `0.1` | DPO strength (dpo only) |
185
189
  | `dpo_warmup_iters` | int ≥0 | `0` | DPO LR warmup (dpo only) |
186
190
  | `dpo_lr_schedule` | `constant` \| `cosine` | `cosine` | DPO LR schedule (dpo only) |
@@ -197,17 +201,14 @@ are `DEFAULT_TRAIN_CONFIG` (trainer.ts:89).
197
201
 
198
202
  | Env var | Set for training | Default | Why |
199
203
  |---|---|---|---|
200
- | `MLX_BUN_FUSED_GELU` | **`0` (required for Gemma)** | on | The fused GeGLU is a **CustomKernel with no gradient (vjp)** ([`fused-geglu-kernel.ts`](../../src/model/fused-geglu-kernel.ts)); the Gemma forward uses it ([`gemma4.ts:277`](../../src/model/gemma4.ts)), so a Gemma (e4b/12B/26B) backward fails unless it's off. MiniCPM5 (Llama-arch SwiGLU) never hits it, so the `.ts` script's MiniCPM5 default doesn't need it. |
201
- | `MLX_BUN_PERF_KERNEL` | **`0` for training** | on | The fused quantized-decode kernel likewise has no vjp. It only fires at decode L=1 (rare in the L>1 training forward), but the e4b recipe sets `0` to be safe. |
202
204
  | `MLX_BUN_TRAIN_ATTN` | **leave unset** | unset → `ops.sdpa` | Default `ops.sdpa` **is** mlx's fused flash-attention kernel — the correct, working path. `flash` selects a *different* hand-rolled O(L)-memory kernel: its two port bugs (dK buffer-transpose, dQ divergent causal barrier) are fixed and FD-validated (`tests/flash-attention.test.ts`, T≤256), but it is ~30× slower and the historical e4b ≥2K crash has **not been re-validated at that scale** since the fix — leave unset unless memory-bound and re-verified. |
203
205
  | `MLX_BUN_MEM_LOG` | `1` to profile | off | Print per-step peak/active/cache memory |
204
206
 
205
- > Important: the trainer itself ([`trainer.ts`](../../src/train/trainer.ts))
206
- > only reads `MLX_BUN_TRAIN_ATTN` and `MLX_BUN_MEM_LOG` — but the **model
207
- > forward** it runs reads `MLX_BUN_FUSED_GELU` / `MLX_BUN_PERF_KERNEL`. The
208
- > trainer does **not** disable those itself, so **the caller must** export
209
- > them. The [e4b recipe](#what-we-actually-run-the-e4b-recipe) does this; if
210
- > you train a Gemma model by hand, set `MLX_BUN_FUSED_GELU=0` yourself.
207
+ > Training needs **no flag sanitization**: the old requirement to export
208
+ > `MLX_BUN_FUSED_GELU=0` / `MLX_BUN_PERF_KERNEL=0` (the no-vjp custom
209
+ > kernels) is gone — those kernels and their env vars were deleted
210
+ > 2026-07-05 ([unified-engine-frontier-plan.md](../design/unified-engine-frontier-plan.md)).
211
+ > Exporting them in an old recipe is harmless but does nothing.
211
212
 
212
213
  ## What we actually run (the e4b recipe)
213
214
 
@@ -215,7 +216,7 @@ Everything above is the full surface (*what you can do*). In practice the
215
216
  fine-tune we run is [`scripts/ft-e4b-v2.sh`](../../scripts/ft-e4b-v2.sh):
216
217
  e4b (gemma-4-e4b-it-OptiQ-4bit, pinned snapshot) on the lucien `chunk-v2-500`
217
218
  curated set (450 train convs) through the segmented-backward trainer. It wraps
218
- `chunk-finetune.ts` with the e4b-required env and a two-step workflow:
219
+ `chunk-finetune.ts` with a two-step workflow:
219
220
 
220
221
  ```bash
221
222
  scripts/ft-e4b-v2.sh probe # 2-iter memory/stability check (~1 min) — RUN FIRST
@@ -233,12 +234,10 @@ What the recipe pins (and why it differs from the bare defaults):
233
234
  | `SEG` | `4` | **segmented backward**, 4 layers/segment — so 8K-ctx activations fit |
234
235
  | `RANK` / `SCALE` / `LR` | `16` / `20` / `1e-5` | task-tuned |
235
236
  | `ITERS` | `2` (probe) / `900` (train) | ~2 epochs over 450 examples |
236
- | `MLX_BUN_PERF_KERNEL` / `MLX_BUN_FUSED_GELU` | `0` / `0` | **required** — the fused kernels have no vjp (see env table) |
237
237
  | attention | default `ops.sdpa` | mlx's fused flash kernel; **not** `MLX_BUN_TRAIN_ATTN=flash` (~30× slower; e4b ≥2K unvalidated since the kernel fix) |
238
238
 
239
- The two non-negotiables for e4b: **segmented backward** (`SEG>0`, so the
240
- long-context activations fit) and the **fused kernels off** (so the backward
241
- has gradients). Always run `probe` before `train`.
239
+ The non-negotiable for e4b: **segmented backward** (`SEG>0`, so the
240
+ long-context activations fit). Always run `probe` before `train`.
242
241
 
243
242
  ## Methodology
244
243
 
@@ -296,6 +295,15 @@ A finished run writes a PEFT-compatible adapter directory:
296
295
  - `optiq_lora_config.json` — mlx-bun/optiq adapter metadata (per-layer ranks)
297
296
  - `adapter_config.json` — PEFT-compatible config
298
297
 
298
+ The save completes only after both config files are written, including
299
+ eval-step checkpoints, so a successful process exit always leaves an
300
+ immediately mountable directory. Serving accepts both mlx-lm tensor layout
301
+ (`[in, rank]` / `[rank, out]`) and standard PEFT layout
302
+ (`[rank, in]` / `[out, rank]`, including `base_model.model.*` names).
303
+ Standard PEFT `use_rslora` metadata is honored as
304
+ `lora_alpha / sqrt(rank)`, and saved rsLoRA adapters emit that metadata so
305
+ their training and serving scales agree.
306
+
299
307
  When `save_checkpoints: true`, each eval step also writes
300
308
  `checkpoints/step-<NNNNN>-val<loss>/` and a durable `metrics.json` (config,
301
309
  wall seconds, peak GB, final/best train+val loss, full val trajectory).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mlx-bun",
3
- "version": "0.0.10",
3
+ "version": "0.0.12",
4
4
  "description": "Native MLX inference for Bun on Apple Silicon — local LLM server (OpenAI/Anthropic-compatible) and TypeScript library. No Python.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -55,13 +55,15 @@
55
55
  "devDependencies": {
56
56
  "@anthropic-ai/sdk": "^0.104.1",
57
57
  "@types/bun": "^1.3.14",
58
+ "happy-dom": "^20.10.6",
58
59
  "openai": "^6.42.0"
59
60
  },
60
61
  "dependencies": {
61
- "@earendil-works/pi-coding-agent": "0.80.2",
62
- "@earendil-works/pi-ai": "0.80.2",
62
+ "@earendil-works/pi-ai": "0.80.3",
63
+ "@earendil-works/pi-coding-agent": "0.80.3",
63
64
  "@huggingface/jinja": "^0.5.9",
64
65
  "@huggingface/tokenizers": "^0.1.3",
66
+ "@mlc-ai/web-xgrammar": "^0.1.27",
65
67
  "fast-png": "^8.0.0",
66
68
  "typebox": "1.1.38"
67
69
  }
package/src/anthropic.ts CHANGED
@@ -150,6 +150,19 @@ export function anthropicToChatBody(body: AnthropicRequest): Record<string, unkn
150
150
  content: flattenText(block.content),
151
151
  });
152
152
  break;
153
+ case "audio":
154
+ case "input_audio":
155
+ case "audio_url":
156
+ // The Anthropic Messages protocol has no audio input block type.
157
+ // Reject loudly (the server maps this throw to a 400
158
+ // invalid_request_error) instead of silently dropping the clip —
159
+ // the model answering as if no audio was sent is worse than an
160
+ // error. Audio is served on the OpenAI surface.
161
+ throw new Error(
162
+ `"${block.type}" content blocks are not part of the Anthropic ` +
163
+ `Messages protocol — send audio to the OpenAI endpoint ` +
164
+ `/v1/chat/completions as an input_audio content part`,
165
+ );
153
166
  default:
154
167
  if (block.text) textParts.push(block.text);
155
168
  }
@@ -456,58 +469,68 @@ export function translateOpenAiSse(
456
469
  const dec = new TextDecoder();
457
470
  let buf = "";
458
471
  let finalized = false;
472
+ let reader: ReadableStreamDefaultReader<Uint8Array> | null = null;
473
+ let cancelled = false;
459
474
  return new ReadableStream<Uint8Array>({
460
- async start(controller) {
475
+ start(controller) {
461
476
  const emit = (frames: string[]) => {
462
477
  for (const f of frames) controller.enqueue(enc.encode(f));
463
478
  };
464
- const reader = upstream.getReader();
465
- try {
466
- while (true) {
467
- const { done, value } = await reader.read();
468
- if (done) break;
469
- buf += dec.decode(value, { stream: true });
470
- let sep: number;
471
- while ((sep = buf.indexOf("\n\n")) !== -1) {
472
- const frame = buf.slice(0, sep);
473
- buf = buf.slice(sep + 2);
474
- const data = frame
475
- .split("\n")
476
- .find((l) => l.startsWith("data: "))
477
- ?.slice(6);
478
- if (data == null) continue;
479
- if (data === "[DONE]" || data === '"[DONE]"') {
480
- if (!finalized) {
481
- finalized = true;
482
- emit(translator.finalize());
479
+ reader = upstream.getReader();
480
+ void (async () => {
481
+ try {
482
+ while (!cancelled) {
483
+ const { done, value } = await reader!.read();
484
+ if (done) break;
485
+ buf += dec.decode(value, { stream: true });
486
+ let sep: number;
487
+ while ((sep = buf.indexOf("\n\n")) !== -1) {
488
+ const frame = buf.slice(0, sep);
489
+ buf = buf.slice(sep + 2);
490
+ const data = frame
491
+ .split("\n")
492
+ .find((l) => l.startsWith("data: "))
493
+ ?.slice(6);
494
+ if (data == null) continue;
495
+ if (data === "[DONE]" || data === '"[DONE]"') {
496
+ if (!finalized) {
497
+ finalized = true;
498
+ emit(translator.finalize());
499
+ }
500
+ continue;
483
501
  }
484
- continue;
485
- }
486
- const parsed = JSON.parse(data);
487
- if (parsed?.error) {
488
- emit([
489
- sse("error", {
490
- type: "error",
491
- error: { type: "api_error", message: parsed.error.message ?? "generation failed" },
492
- }),
493
- ]);
494
- continue;
502
+ const parsed = JSON.parse(data);
503
+ if (parsed?.error) {
504
+ emit([
505
+ sse("error", {
506
+ type: "error",
507
+ error: { type: "api_error", message: parsed.error.message ?? "generation failed" },
508
+ }),
509
+ ]);
510
+ continue;
511
+ }
512
+ emit(translator.addChunk(parsed));
495
513
  }
496
- emit(translator.addChunk(parsed));
497
514
  }
515
+ if (!cancelled && !finalized) emit(translator.finalize());
516
+ } catch (e) {
517
+ if (!cancelled)
518
+ emit([
519
+ sse("error", {
520
+ type: "error",
521
+ error: { type: "api_error", message: (e as Error).message },
522
+ }),
523
+ ]);
524
+ } finally {
525
+ reader?.releaseLock();
526
+ reader = null;
527
+ if (!cancelled) controller.close();
498
528
  }
499
- if (!finalized) emit(translator.finalize());
500
- } catch (e) {
501
- emit([
502
- sse("error", {
503
- type: "error",
504
- error: { type: "api_error", message: (e as Error).message },
505
- }),
506
- ]);
507
- } finally {
508
- reader.releaseLock();
509
- controller.close();
510
- }
529
+ })();
530
+ },
531
+ async cancel(reason) {
532
+ cancelled = true;
533
+ await reader?.cancel(reason);
511
534
  },
512
535
  });
513
536
  }