@mlx-node/lm 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.
@@ -12,12 +12,12 @@
12
12
  import { mkdir, mkdtemp, readFile, readdir, rm, stat, symlink, writeFile } from 'node:fs/promises';
13
13
  import { tmpdir } from 'node:os';
14
14
  import { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
15
- /** Every chat-capable family currently discovered by `@mlx-node/agent`. */
16
- export const AGENT_PAGED_MODEL_TYPES = ['qwen3', 'qwen3_5', 'qwen3_5_moe', 'gemma4', 'lfm2', 'lfm2_moe'];
15
+ import { CHAT_FAMILY_IDS } from '../family-data.js';
17
16
  /** Families historically forced paged by `mlx launch claude`. */
18
17
  export const QWEN35_PAGED_MODEL_TYPES = ['qwen3_5', 'qwen3_5_moe'];
19
18
  const QWEN35_CACHE_FLOOR_MODEL_TYPES = new Set(QWEN35_PAGED_MODEL_TYPES);
20
19
  const DEFAULT_QWEN35_PAGED_CACHE_MB = 16_384;
20
+ const DEFAULT_QWEN35_PAGED_CACHE_INITIAL_MB = 2_048;
21
21
  const QWEN35_MTP_DRAFTER_DIR = 'mtp-drafter';
22
22
  const QWEN35_DENSE_NESTED_MTP_SIDECAR = 'mtp/weights.safetensors';
23
23
  /**
@@ -38,7 +38,7 @@ export class PagedConfigOverrideManager {
38
38
  cleanupPromise;
39
39
  disposed = false;
40
40
  constructor(options = {}) {
41
- this.modelTypes = new Set(options.modelTypes ?? AGENT_PAGED_MODEL_TYPES);
41
+ this.modelTypes = new Set(options.modelTypes ?? CHAT_FAMILY_IDS);
42
42
  this.tempDirPrefix = options.tempDirPrefix ?? 'mlx-paged-overrides-';
43
43
  this.preserveEmbeddedGemmaDraft = options.preserveEmbeddedGemmaDraft ?? false;
44
44
  }
@@ -108,12 +108,20 @@ export class PagedConfigOverrideManager {
108
108
  const pagedEnabled = config.use_block_paged_cache === true;
109
109
  const configuredMemoryMb = positiveNumber(config.paged_cache_memory_mb);
110
110
  const memorySatisfied = cacheFloorMb === undefined || (configuredMemoryMb ?? 0) >= cacheFloorMb;
111
+ // The clone writes `paged_cache_initial_memory_mb = min(initialMb, maxMb)`.
112
+ // A source whose field already equals that value needs no clone; an absent
113
+ // or different field does, or the new start-small default would silently
114
+ // skip every already-paged Qwen3.5 checkpoint (they lack the field).
115
+ const initialMb = QWEN35_CACHE_FLOOR_MODEL_TYPES.has(modelType) ? resolveQwen35InitialMb() : undefined;
116
+ const configuredInitialMb = positiveNumber(config.paged_cache_initial_memory_mb);
117
+ const initialSatisfied = initialMb === undefined ||
118
+ configuredInitialMb === Math.min(initialMb, Math.max(configuredMemoryMb ?? 0, cacheFloorMb ?? 0));
111
119
  // Even an already-paged Gemma config must be cloned when `draft/` exists:
112
120
  // returning the source would expose the draft to native auto-discovery and
113
121
  // trigger the flat-speculation/paged-cache conflict. An authoritative
114
122
  // persist directive that disagrees with the source likewise blocks the
115
123
  // pass-through so the resolved value reaches the loader.
116
- if (pagedEnabled && memorySatisfied && !hasEmbeddedGemmaDraft && !persistOverrideNeeded) {
124
+ if (pagedEnabled && memorySatisfied && initialSatisfied && !hasEmbeddedGemmaDraft && !persistOverrideNeeded) {
117
125
  return modelPath;
118
126
  }
119
127
  // Memoize per (source, resolved family, persist directive): the same
@@ -125,7 +133,7 @@ export class PagedConfigOverrideManager {
125
133
  const existing = this.overrides.get(cacheKey);
126
134
  if (existing !== undefined)
127
135
  return existing;
128
- const pending = this.createOverride(sourcePath, config, cacheFloorMb, modelType, persistPagedCache);
136
+ const pending = this.createOverride(sourcePath, config, cacheFloorMb, initialMb, modelType, persistPagedCache);
129
137
  this.overrides.set(cacheKey, pending);
130
138
  try {
131
139
  return await pending;
@@ -155,7 +163,7 @@ export class PagedConfigOverrideManager {
155
163
  await rm(root, { recursive: true, force: true }).catch(() => undefined);
156
164
  }
157
165
  }
158
- async createOverride(sourcePath, sourceConfig, cacheFloorMb, modelType, persistPagedCache) {
166
+ async createOverride(sourcePath, sourceConfig, cacheFloorMb, initialMb, modelType, persistPagedCache) {
159
167
  const root = await this.getRoot();
160
168
  const overrideDir = await mkdtemp(join(root, 'model-'));
161
169
  const config = {
@@ -173,6 +181,14 @@ export class PagedConfigOverrideManager {
173
181
  if (cacheFloorMb !== undefined) {
174
182
  config.paged_cache_memory_mb = Math.max(positiveNumber(sourceConfig.paged_cache_memory_mb) ?? 0, cacheFloorMb);
175
183
  }
184
+ if (initialMb !== undefined) {
185
+ // Authoritative initial (grow-on-demand) budget, clamped to the max just
186
+ // written above so it can never exceed the pool ceiling. `initialMb` is
187
+ // always >= 1 and the max is floored by `cacheFloorMb` (>= 1) for the
188
+ // same families, so this never writes 0 — which the native loader rejects.
189
+ const maxMb = positiveNumber(config.paged_cache_memory_mb) ?? 0;
190
+ config.paged_cache_initial_memory_mb = Math.min(initialMb, maxMb);
191
+ }
176
192
  await writeFile(join(overrideDir, 'config.json'), JSON.stringify(config, null, 2), 'utf-8');
177
193
  const sourceEntries = await readdir(sourcePath);
178
194
  for (const name of sourceEntries) {
@@ -289,3 +305,10 @@ function resolveQwen35CacheFloorMb() {
289
305
  const parsed = Number.parseInt(raw, 10);
290
306
  return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_QWEN35_PAGED_CACHE_MB;
291
307
  }
308
+ function resolveQwen35InitialMb() {
309
+ const raw = process.env.MLX_PAGED_CACHE_INITIAL_MB;
310
+ if (raw == null || raw === '')
311
+ return DEFAULT_QWEN35_PAGED_CACHE_INITIAL_MB;
312
+ const parsed = Number.parseInt(raw, 10);
313
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_QWEN35_PAGED_CACHE_INITIAL_MB;
314
+ }
@@ -4,10 +4,8 @@
4
4
  * Supports both dense and MoE variants. MoE fields are optional -
5
5
  * when `numExperts` is undefined, the model uses dense MLP layers.
6
6
  */
7
- import type { Qwen35Config as RustQwen35Config, Qwen35GenerationConfig as RustQwen35GenerationConfig, Qwen35GenerationResult as RustQwen35GenerationResult } from '@mlx-node/core';
7
+ import type { Qwen35Config as RustQwen35Config } from '@mlx-node/core';
8
8
  export type Qwen35Config = RustQwen35Config;
9
- export type Qwen35GenerationConfig = RustQwen35GenerationConfig;
10
- export type Qwen35GenerationResult = RustQwen35GenerationResult;
11
9
  /**
12
10
  * Default configurations for common Qwen3.5 models
13
11
  */
@@ -1 +1 @@
1
- {"version":3,"file":"qwen3_5-configs.d.ts","sourceRoot":"","sources":["../../src/models/qwen3_5-configs.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EACV,YAAY,IAAI,gBAAgB,EAChC,sBAAsB,IAAI,0BAA0B,EACpD,sBAAsB,IAAI,0BAA0B,EACrD,MAAM,gBAAgB,CAAC;AAExB,MAAM,MAAM,YAAY,GAAG,gBAAgB,CAAC;AAC5C,MAAM,MAAM,sBAAsB,GAAG,0BAA0B,CAAC;AAChE,MAAM,MAAM,sBAAsB,GAAG,0BAA0B,CAAC;AAEhE;;GAEG;AACH,eAAO,MAAM,cAAc,EAAE;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,YAAY,CAAA;CA8BzD,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,CAM1D"}
1
+ {"version":3,"file":"qwen3_5-configs.d.ts","sourceRoot":"","sources":["../../src/models/qwen3_5-configs.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,YAAY,IAAI,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAEvE,MAAM,MAAM,YAAY,GAAG,gBAAgB,CAAC;AAE5C;;GAEG;AACH,eAAO,MAAM,cAAc,EAAE;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,YAAY,CAAA;CA8BzD,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,CAM1D"}
package/dist/stream.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Gemma4Model as Gemma4ModelNative, Lfm2Model as Lfm2ModelNative, Qwen3Model as Qwen3ModelNative, Qwen35Model as Qwen35ModelNative, Qwen35MoeModel as Qwen35MoeModelNative } from "@mlx-node/core";
1
+ import { Gemma4Model as Gemma4ModelNative, Lfm2Model as Lfm2ModelNative, MuseGlimmerModel as MuseGlimmerModelNative, NemotronHModel as NemotronHModelNative, Qwen3Model as Qwen3ModelNative, Qwen35Model as Qwen35ModelNative, Qwen35MoeModel as Qwen35MoeModelNative } from "@mlx-node/core";
2
2
  import type { ChatStreamChunk, ChatStreamHandle, PerformanceMetrics, ToolCallResult } from "@mlx-node/core";
3
3
  import type { SessionCapableModel } from "./chat-session.js";
4
4
  export interface ChatStreamDelta {
@@ -184,12 +184,14 @@ type ResolvedApplyTemplate<O extends StreamingModelOptions> = O extends {
184
184
  } ? O["applyTemplate"] : O["recordModelPath"];
185
185
  /** @internal Method names whose callback ABI is replaced by the generator wrapper. */
186
186
  export type NativeStreamingMethod = keyof NativeStreamingInstance;
187
- type StreamingReplacementMethod<O extends StreamingModelOptions> = NativeStreamingMethod | (ResolvedApplyTemplate<O> extends true ? "applyChatTemplate" : never);
187
+ type NativeSessionReplacementMethod = "chatSessionStart" | "chatSessionContinue" | "chatSessionContinueTool" | "beginChatSessionStart" | "beginChatSessionContinue" | "beginChatSessionContinueTool";
188
+ type StreamingReplacementMethod<O extends StreamingModelOptions> = NativeStreamingMethod | NativeSessionReplacementMethod | (ResolvedApplyTemplate<O> extends true ? "applyChatTemplate" : never);
188
189
  /**
189
190
  * Instance surface of a generated streaming wrapper. Only methods replaced at
190
191
  * runtime are removed from the native instance: the three callback streaming
191
- * methods, plus `applyChatTemplate` when the wrapper installs its path-backed
192
- * implementation. Intersecting the remaining native surface with
192
+ * methods, the internal operation methods, plus `applyChatTemplate` when the
193
+ * wrapper installs its path-backed implementation. Intersecting the remaining
194
+ * native surface with
193
195
  * `SessionCapableModel` preserves required native capabilities such as
194
196
  * `hasBlockPagedCache()` while exposing the generator streaming signatures.
195
197
  *
@@ -225,7 +227,7 @@ declare const Qwen35Model_base: {
225
227
  new (): StreamingInstance<typeof Qwen35ModelNative, {
226
228
  readonly recordModelPath: true;
227
229
  }>;
228
- load(path: string): Promise<StreamingInstance<typeof Qwen35ModelNative, {
230
+ load(path: string, options?: import("@mlx-node/core").Qwen35LoadOptions | null | undefined): Promise<StreamingInstance<typeof Qwen35ModelNative, {
229
231
  readonly recordModelPath: true;
230
232
  }>>;
231
233
  };
@@ -263,6 +265,17 @@ declare const Lfm2Model_base: {
263
265
  /** LFM2 model (text-only) — see {@link Qwen35Model} for the wrapper shape. */
264
266
  export declare class Lfm2Model extends Lfm2Model_base {
265
267
  }
268
+ declare const NemotronHModel_base: {
269
+ new (): StreamingInstance<typeof NemotronHModelNative, {
270
+ readonly recordModelPath: true;
271
+ }>;
272
+ load(modelPath: string): Promise<StreamingInstance<typeof NemotronHModelNative, {
273
+ readonly recordModelPath: true;
274
+ }>>;
275
+ };
276
+ /** Nemotron 3.5 Lightning (text-only) — see {@link Qwen35Model} for the wrapper shape. */
277
+ export declare class NemotronHModel extends NemotronHModel_base {
278
+ }
266
279
  declare const Gemma4Model_base: {
267
280
  new (config: import("@mlx-node/core").Gemma4Config): StreamingInstance<typeof Gemma4ModelNative, {
268
281
  readonly recordModelPath: true;
@@ -274,6 +287,17 @@ declare const Gemma4Model_base: {
274
287
  /** Gemma4 model (text-only) — see {@link Qwen35Model} for the wrapper shape. */
275
288
  export declare class Gemma4Model extends Gemma4Model_base {
276
289
  }
290
+ declare const MuseGlimmerModel_base: {
291
+ new (): StreamingInstance<typeof MuseGlimmerModelNative, {
292
+ readonly recordModelPath: true;
293
+ }>;
294
+ load(modelPath: string): Promise<StreamingInstance<typeof MuseGlimmerModelNative, {
295
+ readonly recordModelPath: true;
296
+ }>>;
297
+ };
298
+ /** Muse-Glimmer text model with embedded DFlash speculative decoding. */
299
+ export declare class MuseGlimmerModel extends MuseGlimmerModel_base {
300
+ }
277
301
  declare const Qwen3Model_base: {
278
302
  new (): StreamingInstance<typeof Qwen3ModelNative, {
279
303
  readonly recordModelPath: true;
@@ -1 +1 @@
1
- {"version":3,"file":"stream.d.ts","sourceRoot":"","sources":["../src/stream.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,WAAW,IAAI,iBAAiB,EAChC,SAAS,IAAI,eAAe,EAE5B,UAAU,IAAI,gBAAgB,EAC9B,WAAW,IAAI,iBAAiB,EAChC,cAAc,IAAI,oBAAoB,EACvC,MAAM,gBAAgB,CAAC;AACxB,OAAO,KAAK,EAGV,eAAe,EACf,gBAAgB,EAChB,kBAAkB,EAElB,cAAc,EACf,MAAM,gBAAgB,CAAC;AAExB,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAE7D,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,KAAK,CAAC;IACZ,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,IAAI,CAAC;IACX,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,cAAc,EAAE,CAAC;IAC5B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,2EAA2E;IAC3E,eAAe,EAAE,OAAO,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,kBAAkB,CAAC;CAClC;AAED,MAAM,MAAM,eAAe,GAAG,eAAe,GAAG,eAAe,CAAC;AAKhE,KAAK,oBAAoB,GAAG,eAAe,GAAG,gBAAgB,CAAC;AAE/D,UAAU,qBAAqB;IAC7B,KAAK,EAAE,oBAAoB,CAAC;IAC5B;;;OAGG;IACH,wBAAwB,CAAC,EAAE,MAAM,CAAC;CACnC;AA6DD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,wBAAuB,cAAc,CACnC,SAAS,EAAE,CACT,QAAQ,EAAE,CAAC,GAAG,EAAE,KAAK,GAAG,IAAI,EAAE,KAAK,EAAE,eAAe,KAAK,IAAI,KAC1D,OAAO,CAAC,gBAAgB,CAAC,EAC9B,MAAM,CAAC,EAAE,WAAW,GACnB,cAAc,CAAC,eAAe,CAAC,CA+JjC;AAcD;;;;;;;;;;GAUG;AACH,MAAM,WAAW,uBAAuB;IACtC,sBAAsB,EAAE,CAAC,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,OAAO,CAAC,gBAAgB,CAAC,CAAC;IACxE,yBAAyB,EAAE,CAAC,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAC3E,6BAA6B,EAAE,CAC7B,GAAG,IAAI,EAAE,KAAK,EAAE,KACb,OAAO,CAAC,gBAAgB,CAAC,CAAC;CAChC;AAED;;;;;;;;;;GAUG;AACH,UAAU,mBAAmB;IAK3B,KAAK,GAAG,IAAI,EAAE,KAAK,EAAE,GAAG,uBAAuB,CAAC;IAKhD,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACzC,SAAS,EAAE,uBAAuB,CAAC;CACpC;AAED,mDAAmD;AACnD,UAAU,qBAAqB;IAC7B;;;;;;OAMG;IACH,eAAe,EAAE,OAAO,CAAC;IACzB;;;;;;OAMG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;;;OAIG;IACH,qBAAqB,CAAC,EAAE,qBAAqB,CAAC;IAC9C;;;;OAIG;IACH,sBAAsB,CAAC,EAAE,OAAO,CAAC;CAClC;AAED;;;;;GAKG;AACH,MAAM,MAAM,cAAc,GAAG,mBAAmB,CAAC;AAEjD;;;;;;GAMG;AACH,KAAK,qBAAqB,CAAC,CAAC,SAAS,qBAAqB,IAAI,CAAC,SAAS;IACtE,aAAa,EAAE,OAAO,CAAC;CACxB,GACG,CAAC,CAAC,eAAe,CAAC,GAClB,CAAC,CAAC,iBAAiB,CAAC,CAAC;AAEzB,sFAAsF;AACtF,MAAM,MAAM,qBAAqB,GAAG,MAAM,uBAAuB,CAAC;AAElE,KAAK,0BAA0B,CAAC,CAAC,SAAS,qBAAqB,IAC3D,qBAAqB,GACrB,CAAC,qBAAqB,CAAC,CAAC,CAAC,SAAS,IAAI,GAAG,mBAAmB,GAAG,KAAK,CAAC,CAAC;AAE1E;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,iBAAiB,CAC3B,CAAC,SAAS,mBAAmB,EAC7B,CAAC,SAAS,qBAAqB,IAC7B,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,0BAA0B,CAAC,CAAC,CAAC,CAAC,GACtD,mBAAmB,GACnB,CAAC,qBAAqB,CAAC,CAAC,CAAC,SAAS,IAAI,GAClC,QAAQ,CAAC,IAAI,CAAC,mBAAmB,EAAE,mBAAmB,CAAC,CAAC,GACxD,MAAM,CAAC,CAAC;AAEd;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,kBAAkB,CAChC,CAAC,SAAS,mBAAmB,EAC7B,KAAK,CAAC,CAAC,SAAS,qBAAqB,EAErC,WAAW,EAAE,CAAC,EACd,IAAI,EAAE,CAAC,GACN;IAcD,KAAK,GAAG,IAAI,EAAE,qBAAqB,CAAC,CAAC,CAAC,GAAG,iBAAiB,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IACjE,IAAI,CAAC,GAAG,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;CACxE,CAqIA;;;;;;;;;AAED;;;;;;;GAOG;AACH,qBAAa,WAAY,SAAQ,gBAE/B;CAAG;;;;;;;;;AAEL,yEAAyE;AACzE,qBAAa,cAAe,SAAQ,mBAElC;CAAG;;;;;;;;;;;AAEL,8EAA8E;AAC9E,qBAAa,SAAU,SAAQ,cAG7B;CAAG;;;;;;;;;AAEL,gFAAgF;AAChF,qBAAa,WAAY,SAAQ,gBAE/B;CAAG;;;;;;;;;;;AAEL;;;;;;GAMG;AACH,qBAAa,UAAW,SAAQ,eAG9B;CAAG"}
1
+ {"version":3,"file":"stream.d.ts","sourceRoot":"","sources":["../src/stream.ts"],"names":[],"mappings":"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"}
package/dist/stream.js CHANGED
@@ -1,13 +1,10 @@
1
1
  import { join } from "node:path";
2
- import { Gemma4Model as Gemma4ModelNative, Lfm2Model as Lfm2ModelNative, Qwen3Tokenizer, Qwen3Model as Qwen3ModelNative, Qwen35Model as Qwen35ModelNative, Qwen35MoeModel as Qwen35MoeModelNative, } from "@mlx-node/core";
2
+ import { Gemma4Model as Gemma4ModelNative, Lfm2Model as Lfm2ModelNative, MuseGlimmerModel as MuseGlimmerModelNative, NemotronHModel as NemotronHModelNative, Qwen3Tokenizer, Qwen3Model as Qwen3ModelNative, Qwen35Model as Qwen35ModelNative, Qwen35MoeModel as Qwen35MoeModelNative, } from "@mlx-node/core";
3
3
  const modelPathsForTokenizers = new WeakMap();
4
4
  const tokenizerPromises = new WeakMap();
5
5
  function getNativeIsReasoning(chunk) {
6
6
  return typeof chunk.isReasoning === "boolean" ? chunk.isReasoning : undefined;
7
7
  }
8
- function rememberModelPath(model, modelPath) {
9
- modelPathsForTokenizers.set(model, modelPath);
10
- }
11
8
  async function applyChatTemplateFromModelPath(model, messages, addGenerationPrompt, tools, enableThinking, contentPolicy) {
12
9
  const modelPath = modelPathsForTokenizers.get(model);
13
10
  if (modelPath == null) {
@@ -62,8 +59,31 @@ async function applyChatTemplateFromModelPath(model, messages, addGenerationProm
62
59
  * public API — may change without notice.
63
60
  */
64
61
  export async function* _runChatStream(startCall, signal) {
62
+ // The native ThreadsafeFunction uses the same fixed ceiling. This JS-side
63
+ // guard also protects handwritten/test adapters that bypass the Rust glue.
64
+ const maxBufferedEvents = 64;
65
65
  const queue = [];
66
66
  let resolve = null;
67
+ let handle = null;
68
+ let cancelRequested = false;
69
+ let cancelled = false;
70
+ let overflowed = false;
71
+ const cancelOnce = () => {
72
+ if (cancelled)
73
+ return;
74
+ if (handle === null) {
75
+ cancelRequested = true;
76
+ return;
77
+ }
78
+ cancelled = true;
79
+ try {
80
+ handle.cancel();
81
+ }
82
+ catch {
83
+ // Cancellation is best-effort. The distinguished backlog error below
84
+ // remains authoritative for the consumer even if a backend throws.
85
+ }
86
+ };
67
87
  const waitForItem = () => queue.length > 0
68
88
  ? Promise.resolve()
69
89
  : new Promise((r) => {
@@ -77,31 +97,31 @@ export async function* _runChatStream(startCall, signal) {
77
97
  }
78
98
  };
79
99
  const callback = (err, chunk) => {
100
+ if (overflowed)
101
+ return;
102
+ if (queue.length >= maxBufferedEvents) {
103
+ overflowed = true;
104
+ cancelOnce();
105
+ // Keep the queue at its fixed ceiling while guaranteeing the consumer
106
+ // eventually observes why the stream was cancelled.
107
+ queue[queue.length - 1] = {
108
+ error: new Error(`Native chat stream backlog exceeded ${maxBufferedEvents} buffered events`),
109
+ };
110
+ notify();
111
+ return;
112
+ }
80
113
  queue.push(err ? { error: err } : { chunk });
81
114
  notify();
82
115
  };
83
- const handle = await startCall(callback);
116
+ handle = await startCall(callback);
117
+ if (cancelRequested)
118
+ cancelOnce();
84
119
  // Guard against double-cancel. Some native backends throw on a
85
120
  // second `cancel()`; we route every cancel site through this
86
121
  // helper so the abort path (via `triggerAbort`) and the unwind
87
122
  // path (via the `finally` block) don't cancel twice and so any
88
123
  // backend that does throw is swallowed rather than escaping as
89
124
  // an error out of an otherwise-clean early termination.
90
- let cancelled = false;
91
- const cancelOnce = () => {
92
- if (cancelled)
93
- return;
94
- cancelled = true;
95
- try {
96
- handle.cancel();
97
- }
98
- catch {
99
- // Native backend threw on cancel — nothing actionable here.
100
- // Swallow so aborted streams still surface as a clean early
101
- // termination via the synthetic `aborted` marker rather than
102
- // as an unexpected error out of the generator.
103
- }
104
- };
105
125
  // Signal-driven fast-abort. If the signal is already aborted at
106
126
  // attach time we still arm the listener so the synchronous abort
107
127
  // dispatch path runs below (calling `handle.cancel()` after the
@@ -211,6 +231,32 @@ export async function* _runChatStream(startCall, signal) {
211
231
  cancelOnce();
212
232
  }
213
233
  }
234
+ /**
235
+ * Translate the public AbortSignal API to the native two-phase cancellation
236
+ * operation. Callers receive one ordinary Promise and use the same
237
+ * AbortController as fetch and the streaming APIs.
238
+ */
239
+ async function runChatSessionCall(startCall, signal) {
240
+ if (signal.aborted)
241
+ throw new Error("chat session cancelled");
242
+ const call = await startCall();
243
+ let cancelled = false;
244
+ const cancelOnce = () => {
245
+ if (cancelled)
246
+ return;
247
+ cancelled = true;
248
+ call.cancel();
249
+ };
250
+ signal.addEventListener("abort", cancelOnce, { once: true });
251
+ if (signal.aborted)
252
+ cancelOnce();
253
+ try {
254
+ return await call.result();
255
+ }
256
+ finally {
257
+ signal.removeEventListener("abort", cancelOnce);
258
+ }
259
+ }
214
260
  /**
215
261
  * Build the streaming-model subclass for a native chat model class.
216
262
  *
@@ -238,6 +284,7 @@ export function makeStreamingModel(NativeClass, opts) {
238
284
  const nativeStart = NativeClass.prototype.chatStreamSessionStart;
239
285
  const nativeContinue = NativeClass.prototype.chatStreamSessionContinue;
240
286
  const nativeContinueTool = NativeClass.prototype.chatStreamSessionContinueTool;
287
+ const nativeChat = NativeClass.prototype;
241
288
  // `NativeClass` is structurally a constructor; cast to a concrete
242
289
  // constructor type so `class extends` accepts it. Runtime behavior is
243
290
  // unchanged — we extend the real native class.
@@ -251,7 +298,7 @@ export function makeStreamingModel(NativeClass, opts) {
251
298
  }
252
299
  static async load(modelPath, ...rest) {
253
300
  // Forward any trailing family-specific load options verbatim (e.g.
254
- // Gemma4's `Gemma4LoadOptions` with `draftModelPath`); families whose
301
+ // Gemma4/Qwen3.5 `draftModelPath`); families whose
255
302
  // native `load` takes only the path receive no extras. The public
256
303
  // signature is re-narrowed per family via `Parameters<C['load']>` in
257
304
  // the factory return type below.
@@ -260,10 +307,48 @@ export function makeStreamingModel(NativeClass, opts) {
260
307
  // concrete subclass declared per family supplies the prototype and
261
308
  // `instanceof ConcreteSubclass` holds.
262
309
  Object.setPrototypeOf(instance, this.prototype);
263
- if (recordPath)
264
- rememberModelPath(instance, modelPath);
310
+ if (recordPath) {
311
+ const resolvedAssetsPath = instance.modelAssetsPath?.();
312
+ modelPathsForTokenizers.set(instance, resolvedAssetsPath ?? modelPath);
313
+ }
265
314
  return instance;
266
315
  }
316
+ async chatSessionStart(messages, config, signal) {
317
+ if (signal == null) {
318
+ if (nativeChat.chatSessionStart == null) {
319
+ throw new Error("Native model does not implement chatSessionStart");
320
+ }
321
+ return await nativeChat.chatSessionStart.call(this, messages, config);
322
+ }
323
+ if (nativeChat.beginChatSessionStart == null) {
324
+ throw new Error("Native model does not implement beginChatSessionStart");
325
+ }
326
+ return await runChatSessionCall(() => nativeChat.beginChatSessionStart.call(this, messages, config), signal);
327
+ }
328
+ async chatSessionContinue(messages, config, signal) {
329
+ if (signal == null) {
330
+ if (nativeChat.chatSessionContinue == null) {
331
+ throw new Error("Native model does not implement chatSessionContinue");
332
+ }
333
+ return await nativeChat.chatSessionContinue.call(this, messages, config);
334
+ }
335
+ if (nativeChat.beginChatSessionContinue == null) {
336
+ throw new Error("Native model does not implement beginChatSessionContinue");
337
+ }
338
+ return await runChatSessionCall(() => nativeChat.beginChatSessionContinue.call(this, messages, config), signal);
339
+ }
340
+ async chatSessionContinueTool(messages, config, signal) {
341
+ if (signal == null) {
342
+ if (nativeChat.chatSessionContinueTool == null) {
343
+ throw new Error("Native model does not implement chatSessionContinueTool");
344
+ }
345
+ return await nativeChat.chatSessionContinueTool.call(this, messages, config);
346
+ }
347
+ if (nativeChat.beginChatSessionContinueTool == null) {
348
+ throw new Error("Native model does not implement beginChatSessionContinueTool");
349
+ }
350
+ return await runChatSessionCall(() => nativeChat.beginChatSessionContinueTool.call(this, messages, config), signal);
351
+ }
267
352
  // The native methods are callback-based, but `Base` is typed as a
268
353
  // `SessionCapableModel` constructor (whose streaming methods already
269
354
  // return `AsyncGenerator<ChatStreamEvent>`), so these overrides are
@@ -313,11 +398,21 @@ export class Lfm2Model extends makeStreamingModel(Lfm2ModelNative, {
313
398
  replayAssistantRawText: true,
314
399
  }) {
315
400
  }
401
+ /** Nemotron 3.5 Lightning (text-only) — see {@link Qwen35Model} for the wrapper shape. */
402
+ export class NemotronHModel extends makeStreamingModel(NemotronHModelNative, {
403
+ recordModelPath: true,
404
+ }) {
405
+ }
316
406
  /** Gemma4 model (text-only) — see {@link Qwen35Model} for the wrapper shape. */
317
407
  export class Gemma4Model extends makeStreamingModel(Gemma4ModelNative, {
318
408
  recordModelPath: true,
319
409
  }) {
320
410
  }
411
+ /** Muse-Glimmer text model with embedded DFlash speculative decoding. */
412
+ export class MuseGlimmerModel extends makeStreamingModel(MuseGlimmerModelNative, {
413
+ recordModelPath: true,
414
+ }) {
415
+ }
321
416
  /**
322
417
  * Qwen3 (first-gen, text-only) model.
323
418
  *
@@ -344,12 +439,16 @@ function _assertSessionCapable() {
344
439
  const _moe = null;
345
440
  const _lfm2 = null;
346
441
  const _gemma4 = null;
442
+ const _museGlimmer = null;
347
443
  const _qwen3 = null;
444
+ const _nemotronH = null;
348
445
  void _qwen35;
349
446
  void _moe;
350
447
  void _lfm2;
351
448
  void _gemma4;
449
+ void _museGlimmer;
352
450
  void _qwen3;
451
+ void _nemotronH;
353
452
  }
354
453
  void _assertSessionCapable;
355
454
  /** Compile-time guard that both Qwen3.5 native classes and wrappers retain the exact media planner. */
@@ -364,17 +463,21 @@ function _assertExpandedPromptPlannerSurfaces() {
364
463
  void _wrappedMoe;
365
464
  }
366
465
  void _assertExpandedPromptPlannerSurfaces;
367
- /** Compile-time guard that the factory preserves every non-streaming native member. */
466
+ /** Compile-time guard for every native member the factory does not replace. */
368
467
  function _assertPreservedNativeSurfaces() {
369
468
  const _qwen3 = null;
370
469
  const _qwen35 = null;
371
470
  const _moe = null;
372
471
  const _lfm2 = null;
373
472
  const _gemma4 = null;
473
+ const _museGlimmer = null;
474
+ const _nemotronH = null;
374
475
  void _qwen3;
375
476
  void _qwen35;
376
477
  void _moe;
377
478
  void _lfm2;
378
479
  void _gemma4;
480
+ void _museGlimmer;
481
+ void _nemotronH;
379
482
  }
380
483
  void _assertPreservedNativeSurfaces;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mlx-node/lm",
3
- "version": "0.0.10",
3
+ "version": "0.0.12",
4
4
  "homepage": "https://github.com/mlx-node/mlx-node",
5
5
  "bugs": {
6
6
  "url": "https://github.com/mlx-node/mlx-node/issues"
@@ -21,6 +21,10 @@
21
21
  ".": {
22
22
  "types": "./dist/index.d.ts",
23
23
  "import": "./dist/index.js"
24
+ },
25
+ "./family-data": {
26
+ "types": "./dist/family-data.d.ts",
27
+ "import": "./dist/family-data.js"
24
28
  }
25
29
  },
26
30
  "scripts": {
@@ -28,9 +32,9 @@
28
32
  "test": "vite test run"
29
33
  },
30
34
  "dependencies": {
31
- "@mlx-node/core": "0.0.10"
35
+ "@mlx-node/core": "0.0.12"
32
36
  },
33
37
  "devDependencies": {
34
- "@types/node": "^26.0.0"
38
+ "@types/node": "^26.4.0"
35
39
  }
36
40
  }
@@ -1,3 +0,0 @@
1
- /** Compatibility type barrel; model unions are derived from the loader registry. */
2
- export type { LoadableModel, TrainableModel } from './models/model-loader.js';
3
- //# sourceMappingURL=interfaces.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"interfaces.d.ts","sourceRoot":"","sources":["../src/interfaces.ts"],"names":[],"mappings":"AAAA,oFAAoF;AACpF,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC"}
@@ -1 +0,0 @@
1
- export {};